HttpClient等待SendAsync死锁

时间:2017-05-18 14:11:41

标签: c# http asynchronous async-await

我几天来一直在和它斗争,无法弄明白或为什么会这样。我希望你们中的一个主谋能够向我解释。

这是有问题的代码:

public async static Task<RemoteFileDetails> GetRemoteFileDetailsAsync(string strUrl, double dblTimeoutSeconds = 15.0)
{
    try
    {
        using (var hc = new HttpClient())
        {
            hc.Timeout = TimeSpan.FromSeconds(dblTimeoutSeconds);
            using (var h = new HttpRequestMessage(HttpMethod.Head, strUrl))
            {
                h.Headers.UserAgent.ParseAdd(UserAgent);
                using (var rm = await hc.SendAsync(h))
                {
                    rm.EnsureSuccessStatusCode();
                    return new RemoteFileDetails()
                    {
                        Url = strUrl,
                        FileName = rm.Content.Headers.ContentDisposition.FileName,
                        FileSize = rm.Content.Headers.ContentLength.GetValueOrDefault(),
                        LastModified = rm.Content.Headers.LastModified.GetValueOrDefault().LocalDateTime,
                        Valid = true
                    };
                }
            }
        }
    }
    catch (Exception ex)
    {
        System.Windows.MessageBox.Show(ex.ToString());
    }
    return new RemoteFileDetails();
}

所有这一切都是在URL上输入HEAD请求并获取某些标题结果。

这里的问题是大约50%的时间死锁(暂停60秒左右,然后抛出TaskCanceledException),另外50%它的效果非常好。现在,我添加了网络日志记录,这就是死锁时发生的事情:

    System.Net Verbose: 0 : [2928] HttpWebRequest#3567399::HttpWebRequest(https://www.example.com/file.txt#-114720138)
    System.Net Information: 0 : [2928] Current OS installation type is 'Client'.
    System.Net Information: 0 : [2928] RAS supported: True
    System.Net Verbose: 0 : [2928] Exiting HttpWebRequest#3567399::HttpWebRequest() 
    System.Net Verbose: 0 : [2928] HttpWebRequest#3567399::HttpWebRequest(uri: 'https://www.example.com/file.txt', connectionGroupName: '565144')
    System.Net Verbose: 0 : [2928] Exiting HttpWebRequest#3567399::HttpWebRequest() 
    System.Net Verbose: 0 : [5456] HttpWebRequest#3567399::BeginGetResponse()
    System.Net Verbose: 0 : [2244] HttpWebRequest#3567399::Abort()
    System.Net Error: 0 : [2244] Exception in HttpWebRequest#3567399:: - The request was aborted: The request was canceled..
/////// THIS IS WHERE THE 30-60 SECOND PAUSE HAPPENS ////////
    System.Net Error: 0 : [5456] Can't retrieve proxy settings for Uri 'https://www.example.com/file.txt'. Error code: 12180.
    System.Net Verbose: 0 : [5456] ServicePoint#39086322::ServicePoint(www.example.com:443)
    System.Net Information: 0 : [5456] Associating HttpWebRequest#3567399 with ServicePoint#39086322
    System.Net Verbose: 0 : [2244] Exiting HttpWebRequest#3567399::Abort() 
    System.Net Verbose: 0 : [5456] HttpWebRequest#3567399::EndGetResponse()
    System.Net Error: 0 : [5456] Exception in HttpWebRequest#3567399::EndGetResponse - The request was aborted: The request was canceled..
    System.Net Verbose: 0 : [5456] Exiting HttpWebRequest#3567399::BeginGetResponse()   -> ContextAwareResult#36181605

那么,看起来HttpClient()对象正在处理?但是怎么样?为了验证可能是这种情况,我将代码更改为:

private static HttpClient _hc;
public async static Task<RemoteFileDetails> GetRemoteFileDetailsAsync(string strUrl, double dblTimeoutSeconds = 15.0)
{
    _hc = new HttpClient();
    try
    {
        _hc.Timeout = TimeSpan.FromSeconds(dblTimeoutSeconds);
        using (var h = new HttpRequestMessage(HttpMethod.Head, strUrl))
        {
            h.Headers.UserAgent.ParseAdd(UserAgent);
            using (var rm = await _hc.SendAsync(h))
            {
                rm.EnsureSuccessStatusCode();
                return new RemoteFileDetails()
                {
                    Url = strUrl,
                    FileName = rm.Content.Headers.ContentDisposition.FileName,
                    FileSize = rm.Content.Headers.ContentLength.GetValueOrDefault(),
                    LastModified = rm.Content.Headers.LastModified.GetValueOrDefault().LocalDateTime,
                    Valid = true
                };
            }
        }
    }
    catch (Exception ex)
    {
        System.Windows.MessageBox.Show(ex.ToString());
    }
    return new RemoteFileDetails();
}

但它仍然死锁。

有没有解释为什么会发生这种情况?我认为async / await基本上是&#34;暂停&#34;例程,所以无论如何HttpClient都不应该处理。

或者我在这里完全错过了这艘船,而且还有别的东西。

以下是异常的屏幕截图: enter image description here

ETA: 我想重要的是要说明我如何称呼这个。这是一个使用MVVM Light的WPF应用程序。在主视图模型中,我这样称呼它:

    public MainViewModel(IDataService ds)
    {
        _dataService = ds;
        this.Initialize();
    }

    private async void Initialize()
    {
        var det = await RemoteFileUtils.GetRemoteFileDetailsAsync("https://example.com/file.txt");
}

1 个答案:

答案 0 :(得分:2)

问题源于如何被调用。

不要在调用类的构造函数中调用它。除非它在事件处理程序中,否则也要避免使用async void

public class MainViewModel : ViewModelBase {

    public MainViewModel(IDataService ds) {
        _dataService = ds;
        this.Initialize += OnInitialize;
        //Un-comment if you want to call event immediately
        //Initialize(this, EventArgs.Empty);
    }

    public event EventHandler Initialize;

    private async void OnInitialize(object sender, EventArgs e) {
        var det = await RemoteFileUtils.GetRemoteFileDetailsAsync("https://example.com/file.txt");
    }

    //Or call this after class has created.
    public void Ready() {
        Initialize(this, EventArgs.Empty);
    }
}

接下来尝试在每次需要发出请求时都不要创建和制作新的HttpClient。创建一个实例并在整个应用程序的生命周期中使用它。

  

keep an instance of HttpClient for the lifetime of your application for each distinct API that you connect to.

GetRemoteFileDetailsAsync也应该被重构为可注射服务,而不是静态访问。