将基于委托回调的 API 转换为异步
Converting a delegate callback-based API to async
我必须使用 API 看起来像这样的库:
public void Connect();
...
public delegate void ConnectResultDelegate(bool succeeded, string msg);
public ConnectResultDelegate ConnectResultHandler;
调用 Connect()
方法后,将调用 ConnectResultHandler
回调委托。
API 公开了以类似 "request-response" 方式工作的其他方法;我猜委托的原因是这些方法与外部硬件设备交互,并且响应(委托调用)可能不会发生很多毫秒。
我希望我可以用某种方式包装 API,这样我就可以按照 "sequential" 更像 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 问题,但它们的不同之处在于它们的委托被传递给方法,而不是作为单独的属性,这使得包装在任务中相对容易。
有很多 的答案显示了如何将事件或Begin/End 异步操作转换为任务。该代码虽然不遵循任一模型的约定。它类似于基于事件的异步模型 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) 或 Begin/End
.
流程还是一样。 Interop with Other Asynchronous Patterns and Types 中对此进行了描述。
在所有情况下,TaskCompletionSource 都用于创建一个在操作完成时发出信号的任务。
当 class 遵循 APM 约定时,可以在幕后使用 TaskFactory.FromAsync method to convert a Beging/End
pair into a task. FromAsync
uses a TaskCompletionSource 来 return 调用回调时发出信号的任务。 Interop 文档示例是 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;
}
这将允许您在 async
方法中使用 ConnectAsync
,例如:
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 ,这样您就可以像使用服务方法一样使用它 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 看起来像这样的库:
public void Connect();
...
public delegate void ConnectResultDelegate(bool succeeded, string msg);
public ConnectResultDelegate ConnectResultHandler;
调用 Connect()
方法后,将调用 ConnectResultHandler
回调委托。
API 公开了以类似 "request-response" 方式工作的其他方法;我猜委托的原因是这些方法与外部硬件设备交互,并且响应(委托调用)可能不会发生很多毫秒。
我希望我可以用某种方式包装 API,这样我就可以按照 "sequential" 更像 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 问题,但它们的不同之处在于它们的委托被传递给方法,而不是作为单独的属性,这使得包装在任务中相对容易。
有很多 的答案显示了如何将事件或Begin/End 异步操作转换为任务。该代码虽然不遵循任一模型的约定。它类似于基于事件的异步模型 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) 或 Begin/End
.
流程还是一样。 Interop with Other Asynchronous Patterns and Types 中对此进行了描述。 在所有情况下,TaskCompletionSource 都用于创建一个在操作完成时发出信号的任务。
当 class 遵循 APM 约定时,可以在幕后使用 TaskFactory.FromAsync method to convert a Beging/End
pair into a task. FromAsync
uses a TaskCompletionSource 来 return 调用回调时发出信号的任务。 Interop 文档示例是 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;
}
这将允许您在 async
方法中使用 ConnectAsync
,例如:
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 ,这样您就可以像使用服务方法一样使用它 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();
...
}