我们如何在C++/CX中等待C#异步委托函数?

6
这篇文章讲述了不同平台共享代码的C++和 Windows Universal App中的C#之间的通信。我们知道下面是从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 ... 
    }
}

以上内容非常简单明了。但问题在于,在 C# 中,该字符串 GetSomeValueEvent() 执行一些繁重的任务,例如从数据库中读取数据,因此我必须将其改为 async

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

现在问题来了:我该如何让我的C++代码等待这个委托函数的返回呢?换句话说,下面的签名应该如何修改?

    public delegate Platform::String GetSomeValueEventHandler(); 

我已经在Google上搜索了很久,但是找不到这个问题的正确术语。如果您确定它是不可能的,至少知道也是好的。


最终,这就是我们要做的:

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

为了防止死锁,必须确保GetSomeValueEvent不在主线程上运行。

2个回答

3

UPD: 从事件中返回值是一个非常糟糕的主意。考虑其他选项,例如某种观察者模式,但只有一个观察者。

正如@RaymondChen所提到的,对于异步事件,最好使用延迟模式。这是Windows Runtime中常用的模式。

在事件参数中添加GetDeferral()方法,该方法返回一个特殊的延迟对象。该对象必须具有Complete()方法,该方法通知您的代码已完成异步操作。

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

async关键字实际上并不更改函数原型。因此,您只需要修改委托中的返回值以匹配原始返回值(Task<string>)。

但是有个问题:Windows Runtime没有Task<>类型,它是.NET库的一部分。相反,Windows 运行时使用IAsyncOperation<>IAsyncAction来表示异步操作。

所以这是您需要做的:

  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 ...
    }
    

一旦您有两个事件订阅者,这将会出现问题,因为只有其中一个将用于返回值。该类将仅等待一个订阅者任务完成。更好的方法是使用延迟模式。 - Raymond Chen
@RaymondChen 我同意,我应该提到它。但是从事件处理程序返回值是一个非常糟糕的想法,而且我真的不知道如何使用延迟模式来解决这个问题。 - Eldar Dordzhiev
Windows Runtime模式是传递一个具有GetDeferral方法和一些报告结果机制的EventArgs参数。每个委托都需要一个延迟,执行其异步工作,通过报告机制(可能是SetResult方法或将结果附加到集合中)报告结果,然后完成延迟。当最后一个延迟完成时,调用者检查所有报告的结果。 - Raymond Chen
你好,感谢您的建议。不过我采用了另一种解决方案。我同意使用deferral模式而不是async delegate并不是一个好主意,正如Raymond所建议的那样。我最终通过使用Result从C#中删除了async。如果您有兴趣,请随时查看我的修改后的问题(在底部)。 - Yuchen

0
我会使用C++的等效代码:
 var result = GetSomeValueEvent().GetAwaiter().GetResult();

这将为您做的不仅是获取结果,还会通过该任务中发生的所有异常。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接