How do we await for C# async delegate function in C++/CX
Asked Answered
W

2

6

This is about the communication between the C++ (shared code of different platforms) with C# (Windows Universal App). We know that the following is how we make a function call from C++ to C#.

C#

class FooCS
{
    FooCS()
    {
        FooC c = new ref FooC(); 
        c.m_GetSomeValueEvent = GetSomeValueEvent;
        // Some other stuff ...
    }

    string GetSomeValueEvent()
    {
        // Some other stuff ... 
        return "Hello World"; 
    }
}

C++

public delegate Platform::String GetSomeValueEventHandler(); 

class FooC
{
    public:
    event GetSomeValueEventHandler^ m_GetSomeValueEvent; 
    void someFunction(){
        Platform::String^ str = m_GetSomeValueEvent();
        // Some other stuff ... 
    }
}

The above is very straight forward. But the problem is that this string GetSomeValueEvent() in C# do some heavy task such as reading data from the database, and I have to make it async.

    async Task<string> GetSomeValueEvent() {
        // Some other stuff ... 
        return "Hello World"; 
    }

Now here comes the question: How do I make my C++ code wait for the return of this delegate function? In another word, what should be the following signature be modified to?

    public delegate Platform::String GetSomeValueEventHandler(); 

I have been googling around and cannot find the right terminology for this problem. If you know for sure that it is impossible, would be at least nice to know.


Eventually, this is what we do:

    string GetSomeValueEvent() {
        // Convert a async method to non-async one
        var val = someAsyncMethod().Result; 
        // Some other stuff ... 
        return "Hello World"; 
    }

You have to make sure GetSomeValueEvent is not running on the main thread to prevent deadlock.

Wishywashy answered 9/9, 2015 at 20:45 Comment(0)
F
3

UPD: Returning a value from an event is a really BAD idea. Consider the other options like some sort of the observable pattern but with a single observer.

As @RaymondChen mentioned, for async events it's better to use the deferral pattern. It's a commonly used pattern in Windows Runtime.

Add the GetDeferral() method to your event args which returns a special deferral object. This object must have the Complete() method which informs your code that an asynchronous operation has been completed.

===========================================================================

The async keyword actually doesn't change a function prototype. So you just need to alter the return value in your delegate to match the original one (Task<string>).

But there's a catch: Windows Runtime doesn't have the Task<> type, it's a part of TPL which is a .NET library. Instead, Windows Runtime uses IAsyncOperation<> and IAsyncAction to represent asynchronous operations.

So here's what you need to do:

  1. Alter the delegate's signature:

    public delegate Windows::Foundation::IAsyncOperation<Platform::String> GetSomeValueEventHandler();
    
  2. Use the AsAsyncOperation() extension method to convert Task<> to IAsyncOperation<>:

    async Task<string> GetSomeValueEvent()
    {
        // Some other stuff ... 
        return "Hello World"; 
    }
    
    IAsyncOperation<string> GetSomeValueEventWrapper()
    {
        return GetSomeValueEvent().AsAsyncOperation();
    }
    
    FooCS()
    {
        FooC c = new ref FooC(); 
        c.m_GetSomeValueEvent = GetSomeValueEventWrapper;
        // Some other stuff ...
    }
    
Fivespot answered 9/9, 2015 at 21:18 Comment(4)
This breaks down once you have two event subscribers, because only one of them will be used for the return value. The class will wait for only one subscriber task to complete. Better is to use the deferral pattern.Androclinium
@RaymondChen I agree, I should have mentioned it. But returning a value from an event handler is a really bad idea and I honestly don't know how using the deferral patter will resolve that.Fivespot
The Windows Runtime pattern is to pass an EventArgs parameter that has a GetDeferral method and some mechanism for reporting results. Each delegate takes a deferral, does its async work, reports the result via the reporting mechanism (maybe a SetResult method, or by appending the result to a collection), then completing the deferral. When the last deferral completes, the caller inspects all the reported results.Androclinium
Hello, thanks for the suggestions. I adopted a different solution though. I agree it is not a good idea to have async delegate and better use deferral pattern as suggested by Raymond. I ended removing the async from the C# by using Result. If you are interested, feel free to take a look at my modified questions (at the bottom).Wishywashy
C
0

What I would do is use a c++ equivalent of:

 var result = GetSomeValueEvent().GetAwaiter().GetResult();

what that will do for you is not only fetch result, but also passthrough all exceptions happening in that task.

Cydnus answered 10/9, 2015 at 15:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.