繁体   English   中英

当外部输入/输出API提供自己的回调委托时,使用async和await

[英]Using async and await when external Input/output API provides its own callback delegates

我有一个类库(除此之外)充当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()方法在我的库中异步使用GetXmlReportAsync()

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

所述ReportUtilities确实有一个GetResponseAsync() 返回void并提供了AA的委托方法OnReadyCallback一起方法与OnReadyCallback OnReady{get;set;}属性。

我应该补充说.GetResponse()返回一个ReportResponse对象,该对象有一个DownloadAsync()方法,但同样,它返回void而不是Task<byte[]> ReportResponse再次附带OnDownloadSuccessCallback委托和OnDownloadSuccessCallback OnDownloadSuccess { get; set; } OnDownloadSuccessCallback OnDownloadSuccess { get; set; } 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,客户端必须在报告准备就绪时提供回调,但我更愿意将其包装成更熟悉的异步/等待样式API。

我是想在一个圆孔中安装一个方形挂钩,还是我错过了将它包装成异步/等待样式API的简洁方法?

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

您可以使用TaskCompletionSource<string>包装异步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;
}

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

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

我是想在一个圆孔中安装一个方形挂钩,还是我错过了将它包装成异步/等待样式API的简洁方法?

他们使用的模式与EAP非常相似,并且存在将EAP转换为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);
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM