为什么异步方法不会立即返回可等待对象?

4

我认为这是在事件处理程序中异步调用WebClient DownloadData的合理模式:

private async void button1_Click(object sender, EventArgs e)
{
    WebClient wc = new WebClient();
    //wc.Proxy = null;
    byte[] bytes = await wc.DownloadDataTaskAsync("http://www.google.com");
    label1.Text = Encoding.ASCII.GetString(bytes);
}

但我发现,在返回之前DownloadDataTaskAsync会阻塞大约5秒钟(除非取消注释wc.Proxy = null语句)。如果一个方法可以在返回任务之前随意执行非平凡的工作,那么这个方法具有等待的意义吗?

据推测,为了安全起见,我应该永远不要像上面那样调用xAsync方法,而是应该始终将它们包装在Task.Run()中以确保安全。是这样吗?


你正在等待异步操作完成...(使用await关键字) - Bun
1
不,一個 Async 方法確實應該幾乎立即返回任務本身。(我假設你真正說的是方法阻塞,而不是它返回的任務?) 這可能只是一個 WebClient 的 bug。 - Jon Skeet
1
如果您不将代理设置为null或某个值,它会尝试自动检测,这可能需要长达30秒的时间,这可能是5秒等待的原因,与异步无关。在IO活动进行时,您绝对应该像上面那样调用异步方法以释放资源。 - Mant101
是的,看起来 DownloadDataTaskAsync 包装了 HttpWebRequest.BeginGetResponse,然后在返回之前调用 ServicePointManager.FindServicePoint(...),如果传入的代理不为空,则会锁定几秒钟,至少在我的机器上是这样,可能是 Mant101 所说的原因。 - Weyland Yutani
2个回答

7

这是与WebClient/HttpWebRequest相关的已知问题:代理和DNS查找总是同步进行的。这是一个错误,但出于向后兼容性的原因,Microsoft已经决定不修复它。

我建议的第一件事是使用HttpClient。如果这不起作用,并且您需要异步操作,那么可以将调用包装在Task.Run中。


1
好的,谢谢,我明白了。一开始就有人警告我要使用 HttpClient 而不是 WebClient,我应该听从他们的建议... - Weyland Yutani
HttpClient 如何解决这个问题?它使用异步 DNS 查找。 - i3arnon
3
桌面版的.NET中的HttpClient封装了一个基于线程池请求的HttpWebRequest,因此(截至本文编写时),它使用了某种粗暴的方式来强制 DNS/代理等内容异步化。虽然并不完美,但是它还是可行的。我不确定HttpClient在其他平台上是否需要这样做或是否已经这样做了。 - Stephen Cleary

1

原来WebClient.DownloadDataTaskAsync调用了HttpWebRequest.BeginGetResponse

MSDN指出:

BeginGetResponse方法需要一些同步设置任务完成(例如DNS解析、代理检测和TCP套接字连接), 在此方法变成异步之前。因此,这个方法不应该在用户界面(UI)线程上调用, 因为它可能需要相当长的时间(根据网络设置可能需要几分钟)才能完成初始同步设置任务, 在抛出错误异常或方法成功之前。

http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.begingetresponse(v=vs.110).aspx

不幸的是,WebClient.DownloadDataTaskAsync 的 MSDN 文档 中写道:

此操作不会阻塞。

但事实似乎并非如此。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接