简体   繁体   English

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

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

I have a class library that (amongst other things) acts as a wrapper for an external client library for a web API. 我有一个类库(除此之外)充当Web API的外部客户端库的包装器。

My (simplified) code here takes a query string, generates a ReportUtilities object, uses this to download a report, then returns the report string back to the caller: 我的(简化)代码在这里获取一个查询字符串,生成一个ReportUtilities对象,使用它来下载报告,然后将报告字符串返回给调用者:

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

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

The issue is that this method is downloading data from a webservice using a syncronous method. 问题是此方法使用同步方法从Web服务下载数据。 I would like to make this available asyncronously in my library by adding a GetXmlReportAsync() method to my class. 我想通过在我的类中添加GetXmlReportAsync()方法在我的库中异步使用GetXmlReportAsync()

Now, this would be straighforward if the ReportUtilities class provided a GenerateAndDownloadAsync() method returning Task<byte[]> but unfortunately it does not and it is in the external library so I am stuck with what it provides. 现在,如果ReportUtilities类提供了返回Task<byte[]>GenerateAndDownloadAsync()方法,那么这将是直截了当的,但不幸的是它没有,它在外部库中,所以我坚持它提供的内容。

The ReportUtilities class does have a GetResponseAsync() method that returns void and provides a delegate for aa OnReadyCallback method together with an OnReadyCallback OnReady{get;set;} property. 所述ReportUtilities确实有一个GetResponseAsync() 返回void并提供了AA的委托方法OnReadyCallback一起方法与OnReadyCallback OnReady{get;set;}属性。

I should add that .GetResponse() returns a ReportResponse object which does have a DownloadAsync() method but, again, this returns void rather than Task<byte[]> . 我应该补充说.GetResponse()返回一个ReportResponse对象,该对象有一个DownloadAsync()方法,但同样,它返回void而不是Task<byte[]> Again ReportResponse comes with OnDownloadSuccessCallback delegate and OnDownloadSuccessCallback OnDownloadSuccess { get; set; } ReportResponse再次附带OnDownloadSuccessCallback委托和OnDownloadSuccessCallback OnDownloadSuccess { get; set; } OnDownloadSuccessCallback OnDownloadSuccess { get; set; } OnDownloadSuccessCallback OnDownloadSuccess { get; set; } property. OnDownloadSuccessCallback OnDownloadSuccess { get; set; }属性。

It's almost as if the external library authors are 'rolling thier own' async API rather than using the one built into C#? 这几乎就好像外部库作者正在“推动他们自己的”异步API,而不是使用内置于C#的API?

My question is: How can I implement a GetXmlReportAsync() method in my class to make the most efficient use of the asyncronous functions in the client library? 我的问题是:如何在我的类中实现GetXmlReportAsync()方法,以最有效地使用客户端库中的异步函数?

Obviously I could just do: 显然我可以这样做:

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

but then the thread gets tied up with 2 syncronous input/output method calls to the external library: .GetResponse() and .Download() which surely isnt optimal? 但是线程被2个同步的输入/输出方法调用绑定到外部库: .GetResponse().Download()肯定不是最佳的?

Alternatively, I could imagine a situation where I just exposed a similar API to the external library in my own with clients having to provide callbacks for when thier reports are ready but I would much prefer to wrap this up into the more familiar async/await style API. 或者,我可以想象一种情况,我只是在外部库中向外部库公开了类似的API,客户端必须在报告准备就绪时提供回调,但我更愿意将其包装成更熟悉的异步/等待样式API。

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

How can I implement a GetXmlReportAsync() method in my class to make the most efficient use of the asyncronous functions in the client library? 如何在我的类中实现GetXmlReportAsync()方法,以最有效地使用客户端库中的异步函数?

You could wrap the asynchoronous GetResponseAsync call with a TaskCompletionSource<string> . 您可以使用TaskCompletionSource<string>包装异步GetResponseAsync调用。 It would register the delegate once complete and set the completion of the task via SetResult . 它将在完成后注册委托,并通过SetResult设置任务的完成。 It would look something of this sort: 它会看起来像这样:

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

It's almost as if the external library authors are 'rolling thier own' async API rather than using the one built into C#? 这几乎就好像外部库作者正在“推动他们自己的”异步API,而不是使用内置于C#的API?

A not uncommon situation for older libraries, particularly ones that were directly ported from other platforms/languages. 旧库的情况并不罕见,特别是那些直接从其他平台/语言移植的库。

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

The pattern they're using is very similar to EAP , and there's a common pattern for converting EAP to TAP . 他们使用的模式与EAP非常相似,并且存在将EAP转换为TAP的常见模式。 You can do something similar with a few adjustments. 您可以通过一些调整来做类似的事情。

I recommend creating extension methods for the third-party library types that give you nice TAP endpoints, and then building your logic on top of that. 我建议为第三方库类型创建扩展方法,为您提供良好的TAP端点,然后在此基础上构建逻辑。 That way, the TAP method doesn't mix concerns (translating asynchronous patterns, and doing business logic - ie, converting to a string). 这样,TAP方法不会混淆问题(转换异步模式和执行业务逻辑 - 即转换为字符串)。

The ReportUtilities class does have a GetResponseAsync() method that returns void and provides a delegate for aa OnReadyCallback method together with an OnReadyCallback OnReady{get;set;} property. ReportUtilities类具有GetResponseAsync()方法,该方法返回void并为OnReadyCallback方法提供委托以及OnReadyCallback OnReady {get; set;}属性。

Something like this, then: 这样的话,然后:

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

Similarly for the next level: 同样适用于下一个级别:

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

Then your business logic can use the clean TAP endpoints: 然后您的业务逻辑可以使用干净的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