我有一堆慢速函数,本质上是这样的:
private async Task<List<string>> DownloadSomething()
{
var request = 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
}
}
除了调用 WebRequest.Create
之外,这一切都可以很好地异步工作 - 这一行会将 UI 线程卡住几秒钟,这有点破坏了 async/await 的目的。
我已经使用 BackgroundWorker
编写了这段代码,它运行完美并且不会卡住 UI。
不过,创建关于 async/await 的 Web 请求的正确、惯用的方法是什么?或者也许应该使用另一个类?
我见过this nice answer关于异步 WebRequest
,但即使如此,对象本身也是同步创建的。
最佳答案
有趣的是,我没有看到 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()
取消 token 的回调,例如 this 。
Task.Run
之前),您可能不需要 IProgress<I>
图案。只要DownloadSomething()
在 UI 线程上调用,每个 await
之后的每个执行步骤里面DownloadSomething
将在同一个 UI 线程上恢复,因此您可以直接在 awaits
之间更新 UI .
现在,运行整个 DownloadSomething()
通过Task.Run
在池线程上,您必须传递 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 .
关于c# - 如何在不中断 async/await 的情况下创建 HttpWebRequest?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21007112/