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

C# 当外部输入/输出API提供自己的回调委托时,使用async和Wait,c#,asynchronous,async-await,C#,Asynchronous,Async Await,我有一个类库,它(除其他外)充当web API的外部客户端库的包装器 此处我的(简化)代码获取一个查询字符串,生成一个ReportUtilities对象,使用该对象下载一个报告,然后将报告字符串返回给调用者: public string GetXmlReport(string queryString) { ReportUtilities reportUtil = new ReportUtilities(_user,queryString,"XML"); byte[] data

我有一个类库,它(除其他外)充当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
generateAndownloadAsync()
方法,那么这将是非常困难的,但不幸的是,它没有,并且它位于外部库中,因此我无法理解它提供的内容

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

我应该添加
.GetResponse()
返回一个
ReportResponse
对象,该对象确实有一个
downloadsync()
方法,但它返回的是void,而不是
Task
。同样地,
ReportResponse
带有
ondownloadsuccescallback
委托和
ondownloadsuccescallback ondownloadsucces{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);
}
公共异步任务GetXmlReportAsync(字符串查询字符串) { ReportUtilities reportUtil=新的ReportUtilities(_user,queryString,“XML”); byte[]data=wait Task.Run(()=>{return reportUtil.GetResponse().Download();}); 返回Encoding.UTF8.GetString(数据); } 但是线程会被两个对外部库的同步输入/输出方法调用所束缚:
.GetResponse()
.Download()
,这肯定不是最优的

或者,我可以想象这样一种情况:我只是在自己的外部库中公开了一个类似的API,客户机必须在他们的报告准备就绪时提供回调,但我更愿意将其封装到更熟悉的异步/等待样式API中

我是在尝试将一个方形的peg装入一个圆形的孔中,还是缺少一种将其封装到异步/等待样式API中的简洁方法

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

您可以使用
TaskCompletionSource
包装异步
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;
}
公共任务GetXmlReportAsync() { var tcs=new TaskCompletionSource(); ReportUtilities reportUtil=新的ReportUtilities(_user,queryString,“XML”); reportUtil.GetResponseAsync(回调=> { //我估计这将是下载完成后调用的回调 //注意,我假设由回调提供的“Result”属性排序, //将此更改为实际属性 byte[]data=callBack.Result; SetResult(Encoding.UTF8.GetString(数据)); }); 返回tcs.Task; } 这就好像外部库作者正在“滚动他们自己的”异步API,而不是使用C#中内置的API

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

我是在尝试将一个方形的peg装入一个圆形的孔中,还是缺少一种将其封装到异步/等待样式API中的简洁方法

他们使用的模式非常相似,并且有一个通用的模式。你可以做一些类似的调整

我建议为第三方库类型创建扩展方法,为您提供漂亮的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;
}
公共静态任务GetResponseTaskAsync(this ReportUtilities@this)
{
var tcs=new TaskCompletionSource();
@this.OnReady=响应=>
{
//TODO:检查错误,如果发现错误,请调用tcs.TrySetException。
tcs.TrySetResult(响应);
};
@这个.GetResponseAsync();
返回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;
}
公共静态任务下载任务异步(this ReportResponse@this)
{
var tcs=new TaskCompletionSource();
//TODO:如何获取错误?是否存在OnDownloadFailed?
@this.OnDownloadSuccess=结果=>
{
tcs.TrySetResult(结果);
};
@此参数为.downloadsync();
返回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);
}
公共异步任务GetXmlReportAsync(字符串查询字符串) { ReportUtilities reportUtil=新的ReportUtilities(_user,queryString,“XML”); var response=await reportUtil.GetResponseTaskAsync().ConfigureAwait(false); var data=await response.downloadtaskaync().ConfigureAwait(false); 返回Encoding.UTF8.GetString(数据); } 您的问题是“如何等待事件?”?这个问题已经得到了回答,应该对你有帮助