6

我正在尝试启动下载网页内容的异步任务(在 .NET 4.5 上),但不知何故,这个任务永远不会完成。

我的PageDownloader班级:

using System.Net;
using System.Text;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using System;

namespace ParserConsole.WebClient
{
public class PageDownloader
{
    private System.Net.Http.HttpClient _client;

    public PageDownloader()
        : this(Encoding.UTF8) { }

    private Encoding _encoding;

    public PageDownloader(Encoding encoding)
    {
        _encoding = encoding;
        _client = new HttpClient() { Timeout = TimeSpan.FromSeconds(10)};
    }

    private HttpRequestMessage _request;
    private HttpResponseMessage _response;
    private string _responseString;

    public string GetPageData(string link)
    {
        _request = new HttpRequestMessage(HttpMethod.Get, link);
        _request.Headers.Add("User-Agent", "Chrome/21.0.1180.89");
        _request.Headers.Add("Accept", "text/html");


        GetResponse().Wait();
        GetStringFromResponse().Wait();
        return _responseString;            
    }

    private async Task<HttpResponseMessage> GetResponse() {
        return _response = await _client.GetAsync(_request.RequestUri);
    }

    private async Task<string> GetStringFromResponse() {
        return _responseString = await _response.Content.ReadAsStringAsync();
    }

}
}

我通过调用开始下载页面

new PageDownloader().GetPageData(url);

当我尝试调试代码时,一切都很好,直到GetResponse().Wait(). 但不知何故 GetResponse(),任务永远不会完成 - 下一行的断点永远不会到达。我没有例外,应用程序继续运行。有什么建议么?

4

2 回答 2

10

这是您在启动async操作然后阻塞返回的任务时遇到的标准死锁条件。

是讨论该主题的博客文章。

基本上,await调用确保它连接的任务的延续将在您最初所在的上下文中运行(这非常有帮助)但是因为您Wait在相同的上下文中调用它是阻塞的,所以延续永远不会运行,并且延续需要运行等待结束。经典的僵局。

至于修复;通常这意味着您不应该对异步操作进行阻塞等待;这与整个系统的设计背道而驰。您应该“一直异步”。在这种情况下,这意味着GetPageData应该返回 aTask<string>而不是 a string,而不是等待其他返回任务的操作,你应该await对它们进行操作。

现在,话虽如此,有一些方法可以在异步操作上进行阻塞等待而不会死锁。虽然可以做到,但它首先违背了使用 async/await 的目的。使用该系统的主要优点是不会阻塞主要上下文;当你阻塞它时,整个优势就消失了,你不妨一直使用阻塞代码。 async/await实际上更像是一个全有或全无的范例。

以下是我将如何构建该类:

public class PageDownloader
{
    private System.Net.Http.HttpClient _client;
    private Encoding _encoding;

    public PageDownloader()
        : this(Encoding.UTF8) { }

    public PageDownloader(Encoding encoding)
    {
        _encoding = encoding;
        _client = new HttpClient() { Timeout = TimeSpan.FromSeconds(10) };
    }

    public async Task<string> GetPageData(string link)
    {
        HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, link);
        request.Headers.Add("User-Agent", "Chrome/21.0.1180.89");
        request.Headers.Add("Accept", "text/html");

        HttpResponseMessage response = await _client.GetAsync(request.RequestUri);

        return await response.Content.ReadAsStringAsync(); ;
    }
}
于 2012-11-09T19:10:57.260 回答
1

如果您想拥有这样的功能,为什么不这样做。

public string GetPageData(string link)
{
    _request = new HttpRequestMessage(HttpMethod.Get, link);
    _request.Headers.Add("User-Agent", "Chrome/21.0.1180.89");
    _request.Headers.Add("Accept", "text/html");


    var readTask = _client.GetStringAsync(link);
    readTask.Wait();
    return readTask.Result;
}

最好将 Task 一直返回并在调用代码中使用 async/await 处理它。

public Task<string> GetPageData(string link)
{
    _request = new HttpRequestMessage(HttpMethod.Get, link);
    _request.Headers.Add("User-Agent", "Chrome/21.0.1180.89");
    _request.Headers.Add("Accept", "text/html");


    return _client.GetStringAsync(link);
}
于 2012-11-09T19:19:49.187 回答