c# - 当外部输入/输出 API 提供自己的回调委托(delegate)时使用异步和等待

标签 c# asynchronous async-await

我有一个类库(除其他外)充当 Web API 的外部客户端库的包装器。

我的(简化的)代码在这里接受一个查询字符串,生成一个 ReportUtilities对象,使用它来下载报告,然后将报告字符串返回给调用者:

public string GetXmlReport(string queryString)
{
    ReportUtilities reportUtil = new ReportUtilities(_user,queryString,"XML");

    byte[] data = reportUtil.GetResponse().Download();
    return Encoding.UTF8.GetString(data);
}

问题在于此方法使用同步方法从 Web 服务下载数据。我想通过添加 GetXmlReportAsync() 使其在我的库中异步可用我类(class)的方法。

现在,如果 ReportUtilities类提供了一个 GenerateAndDownloadAsync()方法返回 Task<byte[]>但不幸的是它没有,它在外部库中,所以我坚持使用它提供的内容。

ReportUtilities确实有一个GetResponseAsync() 返回 void 并为 OnReadyCallback 提供委托(delegate)的方法方法连同 OnReadyCallback OnReady{get;set;}属性(property)。

我应该补充一点 .GetResponse()返回 ReportResponse确实有 DownloadAsync() 的对象方法,但再次返回 void 而不是 Task<byte[]> .再次ReportResponse附带 OnDownloadSuccessCallback代表和OnDownloadSuccessCallback OnDownloadSuccess { get; set; }属性(property)。

这几乎就像外部库作者正在“滚动他们自己的”异步 API,而不是使用内置于 C# 中的 API?

我的问题是:如何实现 GetXmlReportAsync()我的类中的方法最有效地使用客户端库中的异步函数?

显然我可以这样做:

public async Task<string> GetXmlReportAsync(string queryString)
{
    ReportUtilities reportUtil = new ReportUtilities(_user,queryString,"XML");

    byte[] data = await Task.Run(() => { return reportUtil.GetResponse().Download(); });
    return Encoding.UTF8.GetString(data);
}

但随后线程被 2 个对外部库的同步输入/输出方法调用捆绑在一起:.GetResponse().Download()这肯定不是最优的?

或者,我可以想象这样一种情况,我只是将一个类似的 API 暴露给自己的外部库,而客户必须在他们的报告准备好时提供回调,但我更愿意将其包装到更熟悉的异步中/await 风格的 API。

我是想在圆孔中安装方钉,还是缺少将其包装到异步/等待样式 API 中的巧妙方法?

最佳答案

It's almost as if the external library authors are 'rolling thier own' async API rather than using the one built into C#?

旧库的情况并不少见,尤其是那些直接从其他平台/语言移植的库。

Am I trying to fit a square peg in a round hole or am I missing a neat way to wrap this into an async/await style API?

他们使用的模式与 EAP 非常相似,converting EAP to TAP 有一个通用模式.您可以通过一些调整来做类似的事情。

我建议为第三方库类型创建扩展方法,为您提供良好的 TAP 端点,然后在此基础上构建您的逻辑。这样,TAP 方法就不会混淆问题(转换异步模式和执行业务逻辑 - 即转换为字符串)。

The ReportUtilities class does have a GetResponseAsync() method that returns void and provides a delegate for a a OnReadyCallback method together with an OnReadyCallback OnReady{get;set;} property.

然后是这样的:

public static Task<ReportResponse> GetResponseTaskAsync(this ReportUtilities @this)
{
  var tcs = new TaskCompletionSource<ReportResponse>();
  @this.OnReady = response =>
  {
    // TODO: check for errors, and call tcs.TrySetException if one is found.
    tcs.TrySetResult(response);
  };
  @this.GetResponseAsync();
  return tcs.Task;
}

下一级同样如此:

public static Task<byte[]> DownloadTaskAsync(this ReportResponse @this)
{
  var tcs = new TaskCompletionSource<byte[]>();
  // TODO: how to get errors? Is there an OnDownloadFailed?
  @this.OnDownloadSuccess = result =>
  {
    tcs.TrySetResult(result);
  };
  @this.DownloadAsync();
  return tcs.Task;
}

然后您的业务逻辑可以使用干净的 TAP 端点:

public async Task<string> GetXmlReportAsync(string queryString)
{
  ReportUtilities reportUtil = new ReportUtilities(_user, queryString, "XML");

  var response = await reportUtil.GetResponseTaskAsync().ConfigureAwait(false);
  var data = await response.DownloadTaskAsync().ConfigureAwait(false);
  return Encoding.UTF8.GetString(data);
}

关于c# - 当外部输入/输出 API 提供自己的回调委托(delegate)时使用异步和等待,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30026659/

相关文章:

c# - 复杂的ViewModel- View 无法根据需要的模型建议自动映射值。

c# - 如何检查数组中对元素的数量?

flutter - 在未来的异步调用中,我需要一些指导,包括 flutter 和 Dart ,有时情况会发生困惑

c# - 如何通过 httpresponse 读取以获取返回值 :

node.js - 优化异步查询 Node

c# - 无法开始监视对 'C:\********\global.asax'的更改

c# - 是否可以将现有实例注入(inject) MEF 插件?

javascript - 回调和返回值

c# - 从异步任务返回

c# - List<>.ForEach() 中的线程不安全异步