将基于委托回调的API转换为异步 [英] Converting a delegate callback-based API to async

查看:142
本文介绍了将基于委托回调的API转换为异步的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我必须使用其API如下所示的库:

I have to use a library whose API looks something like this:

public void Connect();

...

public delegate void ConnectResultDelegate(bool succeeded, string msg);
public ConnectResultDelegate ConnectResultHandler;

调用 Connect()方法后,将调用 ConnectResultHandler 回调委托.

After calling the Connect() method, the ConnectResultHandler callback delegate will get called.

API公开了其他以类似请求-响应"方式工作的方法;我想代表的原因是这些方法与外部硬件设备交互,并且响应(委托调用)可能不会持续几毫秒.

The API exposes other methods that work in a similar "request-response" manner; I guess the reason for the delegates is that the methods interact with an external hardware device, and the response (delegate call) may not happen for many milliseconds.

我希望我可以以某种方式包装API,从而允许我以更顺序"的方式使用它,更像是async/await,大致如下:

I was hoping I Could wrap the API in some way that would allow me to use it in a more "sequential" manner that is more like async/await, along the lines of:

void DoSomething()
{
    _library.Connect();
    // Wait for notification that this has completed
    // Do something with the response passed to the delegate callback

    _library.Configure(...);
    // Wait for notification that this has completed
    // Do something with the response
    ..etc..
}

有什么想法吗?重构库本身不是一种选择.

Thoughts? Refactoring the library itself is not an option.

那里有一两个类似的SO问题,但是它们的不同之处在于它们的委托被传递给方法,而不是作为单独的属性传递,因此相对容易包装在Task中.

There are one or two similar SO questions out there, but they differ in that their delegates are passed to the methods, rather than being separate properties, making it relatively easy to wrap in a Task.

推荐答案

很多个答案,它们显示了如何将事件或开始/结束"异步操作转换为任务.该代码虽然没有遵循任何一种模型的约定.它类似于基于事件的异步模型

There are a lot of answers that show how to convert events or Begin/End async operations into tasks. That code though doesn't follow the conventions of either model. It's similar to the Event-based Async model EAP without using an event. If you searched for event to task conversions, you'd find a lot of answers. Delegates arent' used for async operations though, as the convention before EAP was to sue the Asynchronous Programming Model (APM) or Begin/End.

尽管处理过程仍然相同.

The process process is still the same though. It's described in Interop with Other Asynchronous Patterns and Types. In all cases, a TaskCompletionSource is used to create a Task that's signalled when an operation completes.

当该类遵循APM约定时,可以使用 TaskCompletionSource 可以返回一个在调用回调时发出信号的Task.为此,Interop doc示例为 Stream.BeginRead :

When the class follows the APM conventions, one can use the TaskFactory.FromAsync method to convert a Beging/End pair into a task. FromAsync uses a TaskCompletionSource under the covers to return a Task that's signaled when the callback is called. The Interop doc example for this is Stream.BeginRead :

public static Task<int> ReadAsync(this Stream stream, 
                              byte[] buffer, int offset, 
                              int count)
{
    if (stream == null) 
       throw new ArgumentNullException("stream");

    return Task<int>.Factory.FromAsync(stream.BeginRead, 
                                   stream.EndRead, buffer, 
                                   offset, count, null);
}

使用委托类似于使用事件,

Using delegates is similar to using events, which is also shown in the interop article. Adapted to the question, it would look something like this :

public Task<bool> ConnectAsync(ThatService service)
{
    if (service==null) 
        throw new ArgumentNullException(nameof(service));

    var tcs=new TaskCompletionSource<bool>();

    service.ConnectResultHandler=(ok,msg)=>
    {
        if(ok)
        {
            tcs.TrySetResult(true);
        }
        else
        {
            tcs.TrySetException(new Exception(msg));
        }
    };

    return tcs.Task;
}

这将允许您在 async 方法中使用 ConnectAsync ,例如:

This will allow you to use ConnectAsync in an async method, eg :

public async Task MyMethod()
{
    ...
    var ok=await ConnectAsync(_service);
    ...

}

如果 msg 成功包含数据,则可以将 ConnectAsync 更改为:

If msg contains data on success, you could change ConnectAsync to :

public Task<string> ConnectAsync(ThatService service)
{
    if (service==null) 
        throw new ArgumentNullException(nameof(service));

    var tcs=new TaskCompletionSource<string>();

    service.ConnectResultHandler=(ok,msg)=>
    {
        if(ok)
        {
            tcs.TrySetResult(msg);
        }
        else
        {
            tcs.TrySetException(new Exception(msg));
        }
    };

    return tcs.Task;
}

您可以将 ConnectAsync 更改为

You can change ConnectAsync into an extension method which will allow you to use it as if it were a method of your service class :

public static class MyServiceExtensions 
{
    public static Task<string> ConnectAsync(this ThatService service)
    {
        //Same as before
    }
}

并使用它:

public async Task MyMethod()
{
    ...
    var msg=await _service.ConnectAsync();
    ...
}

这篇关于将基于委托回调的API转换为异步的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆