将基于委托回调的API转换为异步

时间:2019-01-10 13:25:52

标签: c# async-await task-parallel-library

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

public void Connect();

...

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

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

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

我希望我可以以某种方式包装API,从而允许我以更“顺序”的方式使用它,更像是async / await,类似于:

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

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

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

1 个答案:

答案 0 :(得分:2)

很多个答案,显示了如何将事件或Begin / End异步操作转换为任务。该代码虽然没有遵循任何一种模型的约定。它类似于不使用事件的基于事件的异步模型EAP。如果您搜索了事件到任务的转换,则会找到很多答案。不过,请委托arent用于异步操作,因为EAP之前的惯例是起诉Asynchronous Programming Model (APM)Begin/End

尽管处理过程仍然相同。在Interop with Other Asynchronous Patterns and Types中有描述。 在所有情况下,都使用TaskCompletionSource创建一个在操作完成时发出信号的Task。

当该类遵循APM约定时,可以使用TaskFactory.FromAsync方法将Beging/End对转换为任务。 FromAsync使用内部的TaskCompletionSource返回调用了回调时已发出信号的Task。为此,Interop doc示例为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);
}

使用委托类似于使用事件,interop article中也显示了事件。适应了这个问题,它看起来像这样:

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

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

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

}

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

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更改为extension method ,使您可以像使用服务类的方法一样使用它:

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

并使用它:

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