【问题标题】:Using async and await when external Input/output API provides its own callback delegates当外部输入/输出 API 提供自己的回调委托时使用 async 和 await
【发布时间】:2015-05-04 20:23:44
【问题描述】:

我有一个类库(除其他外),它充当 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() 方法来使这个在我的库中异步可用。

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

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

我应该补充一点,.GetResponse() 返回一个 ReportResponse 对象,它确实有一个 DownloadAsync() 方法,但同样,它返回的是 void 而不是 Task<byte[]>。同样ReportResponse 带有OnDownloadSuccessCallback 委托和OnDownloadSuccessCallback OnDownloadSuccess { get; set; } 属性。

就好像外部库作者在“滚动他们自己的”异步 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 中的巧妙方法?

【问题讨论】:

  • 您的问题是“如何等待事件?”?那个已经回答了,应该可以帮助你。

标签: c# asynchronous async-await


【解决方案1】:

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

对于较旧的库,尤其是直接从其他平台/语言移植的库来说,这种情况并不少见。

我是想在圆孔中安装一个方形钉,还是我错过了一种将其包装到异步/等待样式 API 中的巧妙方法?

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

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

ReportUtilities 类确实有一个 GetResponseAsync() 方法,该方法返回 void 并为 OnReadyCallback 方法和 OnReadyCallback OnReady{get;set;} 属性提供委托。

类似这样的话:

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

【讨论】:

  • 非常感谢这位斯蒂芬 - 确实非常清楚。顺便说一句 - 您期望的 OnxxxFailed() 回调确实存在 - 我只是将它们从原始问题中删除,以使其尽可能简单
【解决方案2】:

如何在我的类中实现 GetXmlReportAsync() 方法以使 最有效地使用客户端中的异步函数 图书馆?

您可以用TaskCompletionSource&lt;string&gt; 包装异步GetResponseAsync 调用。一旦完成,它将注册委托并通过SetResult 设置任务的完成。它看起来像这样:

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

    reportUtil.GetResponseAsync(callBack => 
    {
        // I persume this would be the callback invoked once the download is done
        // Note, I am assuming sort sort of "Result" property provided by the callback,
        // Change this to the actual property
        byte[] data = callBack.Result;
        tcs.SetResult(Encoding.UTF8.GetString(data));
    });

    return tcs.Task;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-10-17
    • 2011-09-02
    • 2018-12-10
    • 1970-01-01
    • 2021-11-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多