有趣的是,我没有看到 WebRequest.Create 或 HttpClient.PostAsync 的阻塞延迟。这可能与 DNS 解析或代理配置有关,尽管我希望这些操作也可以在内部以异步方式实现。
无论如何,作为一种解决方法,您可以在池线程上启动请求,尽管这不是我通常会做的事情:
private async Task<List<string>> DownloadSomething()
{
var request = await Task.Run(() => {
// WebRequest.Create freezes??
return System.Net.WebRequest.Create("https://valid.url");
});
// ...
using (var ss = await request.GetRequestStreamAsync())
{
await ss.WriteAsync(...);
}
using (var rr = await request.GetResponseAsync())
using (var ss = rr.GetResponseStream())
{
//read stream and return data
}
}
这将使 UI 保持响应,但如果用户想要停止操作,可能很难cancel 它。那是因为您需要已经有一个 WebRequest 实例才能在其上调用 Abort。
使用HttpClient,可以取消,如下所示:
private async Task<List<string>> DownloadSomething(CancellationToken token)
{
var httpClient = new HttpClient();
var response = await Task.Run(async () => {
return await httpClient.PostAsync("https://valid.url", token);
}, token);
// ...
}
使用HttpClient,您还可以在取消令牌上注册httpClient.CancelPendingRequests() 回调,例如this。
[更新] 基于 cmets:在您的原始情况下(在引入
Task.Run 之前)您可能不需要
IProgress<I> 模式。只要在 UI 线程上调用了
DownloadSomething(),
DownloadSomething 内每个
await 之后的每个执行步骤都将在同一个 UI 线程上恢复,因此您可以直接在
awaits 之间更新 UI。
现在,要在池线程上通过Task.Run 运行整个DownloadSomething(),您必须将IProgress<I> 的实例传递给它,例如:
private async Task<List<string>> DownloadSomething(
string url,
IProgress<int> progress,
CancellationToken token)
{
var request = System.Net.WebRequest.Create(url);
// ...
using (var ss = await request.GetRequestStreamAsync())
{
await ss.WriteAsync(...);
}
using (var rr = await request.GetResponseAsync())
using (var ss = rr.GetResponseStream())
{
// read stream and return data
progress.Report(...); // report progress
}
}
// ...
// Calling DownloadSomething from the UI thread via Task.Run:
var progressIndicator = new Progress<int>(ReportProgress);
var cts = new CancellationTokenSource(30000); // cancel in 30s (optional)
var url = "https://valid.url";
var result = await Task.Run(() =>
DownloadSomething(url, progressIndicator, cts.Token), cts.Token);
// the "result" type is deduced to "List<string>" by the compiler
注意,因为DownloadSomething 本身是一个async 方法,它现在作为嵌套任务运行,Task.Run 透明地为您解包。更多信息:Task.Run vs Task.Factory.StartNew。
另请查看:Enabling Progress and Cancellation in Async APIs。