当外部 Input/output API 提供自己的回调委托时使用异步和等待
Using async and await when external Input/output API provides its own callback delegates
我有一个 class 库,它(除其他外)充当 Web 外部客户端库的包装器 API。
我的(简化)代码在这里获取一个查询字符串,生成一个 ReportUtilities
对象,使用它来下载报告,然后 returns 将报告字符串返回给调用者:
public string GetXmlReport(string queryString)
{
ReportUtilities reportUtil = new ReportUtilities(_user,queryString,"XML");
byte[] data = reportUtil.GetResponse().Download();
return Encoding.UTF8.GetString(data);
}
问题是此方法使用同步方法从 Web 服务下载数据。我想通过向我的 class.
添加一个 GetXmlReportAsync()
方法,使它在我的库中异步可用
现在,如果 ReportUtilities
class 提供了一个返回 Task<byte[]>
的 GenerateAndDownloadAsync()
方法,这将是直截了当的,但不幸的是它没有,它在外部库中所以我坚持使用它提供的功能。
ReportUtilities
class有一个GetResponseAsync()
方法那个returnsvoid 并为 OnReadyCallback
方法和 OnReadyCallback OnReady{get;set;}
属性.
方法提供委托
我应该补充一点 .GetResponse()
returns 一个 ReportResponse
对象,它确实有一个 DownloadAsync()
方法,但是,同样,这个 returns void 而不是 Task<byte[]>
。同样 ReportResponse
带有 OnDownloadSuccessCallback
委托和 OnDownloadSuccessCallback OnDownloadSuccess { get; set; }
属性.
这几乎就好像外部库作者是 'rolling thier own' 异步 API 而不是使用 C# 内置的?
我的问题是:如何在 class 中实现 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 个对外部库的同步 input/output 方法调用捆绑在一起:.GetResponse()
和 .Download()
这肯定不是最优的?
或者,我可以想象这样一种情况,我只是将类似的 API 暴露给自己的外部库,客户必须在他们的报告准备就绪时提供回调,但我更愿意包装这个进入更熟悉的 async/await 风格 API.
我是想在圆孔中安装一个方钉,还是缺少将其包装成 async/await 样式 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?
您可以用 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;
}
It's almost as if the external library authors are 'rolling thier own' async API rather than using the one built into C#?
旧库的情况并不少见,尤其是那些直接从其他 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?
他们使用的模式与 EAP, and there's a common pattern for 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);
}
我有一个 class 库,它(除其他外)充当 Web 外部客户端库的包装器 API。
我的(简化)代码在这里获取一个查询字符串,生成一个 ReportUtilities
对象,使用它来下载报告,然后 returns 将报告字符串返回给调用者:
public string GetXmlReport(string queryString)
{
ReportUtilities reportUtil = new ReportUtilities(_user,queryString,"XML");
byte[] data = reportUtil.GetResponse().Download();
return Encoding.UTF8.GetString(data);
}
问题是此方法使用同步方法从 Web 服务下载数据。我想通过向我的 class.
添加一个GetXmlReportAsync()
方法,使它在我的库中异步可用
现在,如果 ReportUtilities
class 提供了一个返回 Task<byte[]>
的 GenerateAndDownloadAsync()
方法,这将是直截了当的,但不幸的是它没有,它在外部库中所以我坚持使用它提供的功能。
ReportUtilities
class有一个GetResponseAsync()
方法那个returnsvoid 并为 OnReadyCallback
方法和 OnReadyCallback OnReady{get;set;}
属性.
我应该补充一点 .GetResponse()
returns 一个 ReportResponse
对象,它确实有一个 DownloadAsync()
方法,但是,同样,这个 returns void 而不是 Task<byte[]>
。同样 ReportResponse
带有 OnDownloadSuccessCallback
委托和 OnDownloadSuccessCallback OnDownloadSuccess { get; set; }
属性.
这几乎就好像外部库作者是 'rolling thier own' 异步 API 而不是使用 C# 内置的?
我的问题是:如何在 class 中实现 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 个对外部库的同步 input/output 方法调用捆绑在一起:.GetResponse()
和 .Download()
这肯定不是最优的?
或者,我可以想象这样一种情况,我只是将类似的 API 暴露给自己的外部库,客户必须在他们的报告准备就绪时提供回调,但我更愿意包装这个进入更熟悉的 async/await 风格 API.
我是想在圆孔中安装一个方钉,还是缺少将其包装成 async/await 样式 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?
您可以用 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;
}
It's almost as if the external library authors are 'rolling thier own' async API rather than using the one built into C#?
旧库的情况并不少见,尤其是那些直接从其他 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?
他们使用的模式与 EAP, and there's a common pattern for 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);
}