使用Task<T>封装本地异步函数

5

我正在尝试编写一个C#包装器,用于调用一个第三方的原生代码库,以便在我们几乎全部使用.NET编写的应用程序中使用,并且我试图保持对C#模式的忠实。这个库中几乎所有的调用都是异步的,因此将所有异步调用都封装成Task<T>对象似乎是合适的做法。以下是一个简化示例,展示了原生库的结构:

delegate void MyCallback(string outputData);

class MyNativeLibrary
{
    public int RegisterCallback(MyCallback callback); // returns -1 on error
    public int RequestData(string inputData);         // returns -1 on error
}

目前,我通过事件订阅来提供我的返回值,但是我认为这将是返回数据的更好方法:

class WrapperAroundNativeCode
{
    public async Task<string> RequestData(string inputData);
}

到目前为止,我还没有找到一个适当的方法来实现这一点,因此我向那些在处理Task<T>对象和async/await模式方面更有经验的人寻求帮助。


3
你如何知道是哪个“RequestData”导致了“MyCallback”请求? - luiscubal
1
你如何接收异常通知? - Stephen Cleary
如何取消回调函数的注册?或者它自动取消注册了吗? - svick
@luiscubal,实际上有几种方法可以注册回调到这个第三方库。我在这里工作了几年才掌握这些技巧! - Joey Herrington
1
@JoeyHerrington:我的意思是没有办法检测异步错误。也就是说,你成功地注册了回调函数,请求也成功通过了,但处理请求时出现了某些错误,你不会收到通知。 - Stephen Cleary
显示剩余3条评论
2个回答

12

要完成这个任务,您需要使用 TaskCompletionSource<TResult> 类。以下是示例代码:

class WrapperAroundNativeCode
{
    public async Task<string> RequestData(string inputData)
    {
        var completionSource = new TaskCompletionSource<string>();
        var result = Native.RegisterCallback(s => completionSource.SetResult(s));
        if(result == -1)
        {
            completionSource.SetException(new SomeException("Failed to set callback"));
            return completionSource.Task;
        }

        result = Native.RequestData(inputData);
        if(result == -1)
            completionSource.SetException(new SomeException("Failed to request data"));
        return completionSource.Task;
    }
}

本答案假设此方法不会发生并发调用。如果发生了并发调用,您将需要某种方式来区分不同的调用。许多API提供了一个userData有效负载,您可以将其设置为每个调用的唯一值,以便您可以进行区分。


看起来你做得很好!我尝试了这个技巧,它似乎运行得非常好。谢谢你的帮助。 - Joey Herrington
抱歉,我只能将一个答案标记为正确的,否则我会将你和Jon Skeet都标记为正确。 - Joey Herrington

9
看起来您正在寻找TaskCompletionSource<T>。您需要创建一个TaskCompletionSource,将其包装在库中,创建一个MyNativeLibrary实例并注册回调,设置任务完成源的结果,然后从同一实例请求数据。如果其中任何一步失败,请在任务完成源上设置错误。然后只需返回TaskCompletionSource<>.Task属性的值给调用者即可。(假设您可以创建MyNativeLibrary的单独实例 - 如果您只能在整个应用程序中创建一个实例,则会变得更加困难。)

谢谢,那个完美地解决了。我还有一个问题:Task 有泛型和非泛型两种版本,但是我只看到了 TaskCompletionSource 的泛型版本。我错过了吗? - Joey Herrington
1
@JoeyHerrington Task<T>继承自Task,因此从Task<T>Task存在隐式转换。您可以简单地构建一个TaskCompletionSource<object>并使用SetResult(null);(或者使用任何虚拟值代替null)。 - luiscubal
是的,那基本上就是我所做的。我只是不知道是否有实际的非泛型类型。 - Joey Herrington

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