Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HttpClient await SendAsync deadlock

I have been battling this for days now and can't figure it out or why it is the way it is. I am hoping one of you masterminds can explain it to me.

This is the code in question:

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();
}

All this does is puts in a HEAD request on a URL and grabs the certain header results.

The issue here is about 50% of the time it deadlocks (pauses for 60 seconds or so, then throws a TaskCanceledException), the other 50% it works wonderfully. Now, I added network logging, and this is what's happening on deadlock:

    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

So, it looks like the HttpClient() object is getting disposed? But how? To verify that this may be the case, I changed the code to this:

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();
}

but it still deadlocks the same.

Is there any explanation is to why this is happening? I thought async/await was basically "pausing" the routine, so the HttpClient should never dispose, anyway.

Or am I missing the boat totally here and it's something else.

Here is a screencap of the exception: enter image description here

ETA: I suppose it's important to show how I am calling this. This is a WPF app using MVVM Light. In the main view model, i am calling this as so:

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

    private async void Initialize()
    {
        var det = await RemoteFileUtils.GetRemoteFileDetailsAsync("https://example.com/file.txt");
}
like image 994
Andy Avatar asked May 31 '26 13:05

Andy


1 Answers

The problem originates with how it is being called.

Do not call that in the constructor of the calling class. Also avoid async void unless it is in an event handler.

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);
    }
}

Next try to not create and dispoase a new HttpClient every time you need to make a request. Create one instance and use that through out the life of the application.

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

GetRemoteFileDetailsAsync should also be refactored to be an injectable service rather than being statically accessed.

like image 157
Nkosi Avatar answered Jun 02 '26 03:06

Nkosi



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!