Avoiding Deadlock with HttpClient

后端 未结 3 1554
失恋的感觉
失恋的感觉 2021-01-03 10:57

What is the best way to use HttpClient and avoid deadlock? I am using the code below, called entirely from synchronous methods, but I concerned it maybe causing

相关标签:
3条回答
  • 2021-01-03 11:20

    I think you mean avoid blocking. Deadlocks refer to situations where two or more threads are all indefinitely waiting for each other to finish.

    To avoid blocking in your sample code, instead of synchronously waiting on Results, you just need to await the non-blocking API calls:

    HttpContent content = new StringContent(json, Encoding.UTF8, "application/json");
    using (HttpClient client = new HttpClient())
    {
        HttpResponseMessage responseMessage = null;
    
        switch (verb)
        {
            case HttpVerb.Put:
                responseMessage = await client.PutAsync(url, content);
                break;
            case HttpVerb.Post:
                responseMessage = await client.PostAsync(url, content);
                break;
            case HttpVerb.Delete:
                responseMessage = await client.DeleteAsync(url);
                break;
            case HttpVerb.Get:
                responseMessage = await client.GetAsync(url);
                break;
        }
    
        if (responseMessage.IsSuccessStatusCode)
        {
            responseContent = await responseMessage.Content.ReadAsStringAsync();
            statusCode = responseMessage.StatusCode;
        }
    }
    
    0 讨论(0)
  • 2021-01-03 11:28

    In my case, when I used await client.GetAsync(), I didnot receive any response, when I tried to observe the problem, the debugger also terminated on the above GetAsync() line. I could only use client.GetAsync().Result to get the reponse correctly, but I saw many saying that it may lead to deadlock like in this post and it is synchronous too. I was particularly using this in UI button click event as well, which is not recommended.

    Finally, I got it working by adding the below three lines, I didnot have those three lines before (maybe that was the problem):

    using (var client = new HttpClient())
    {    
      string relativeURL = "api/blah";
      //These three lines - BaseAddress, DefaultRequestHeaders and DefaultRequestHeaders
      client.BaseAddress = new Uri(Constants.ServiceBaseAddress);
      client.DefaultRequestHeaders.Clear();
      client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
      HttpResponseMessage response = await client.GetAsync(relativeURL);
      ...
    }
    

    NOTE:

    1. The ServiceBaseAddress must end with slash (/) and relative URL must begin without slash (/)
    2. Sample of my Constants.ServiceBaseAddress is https://domainname.org/

    This is working fine now, with async signature and no deadlock worries. Hope this helps somebody.

    0 讨论(0)
  • 2021-01-03 11:39

    It looks like you are trying to run asynchronous code synchronously.

    With WinForms and WPF YOU CANNOT SAFELY DO THIS!

    The only thing you can do is to use async all the way up. Its a known problem with .net async. You can use public async void XXX() methods. But then you don't know when they complete. You should ONLY ever use async void when coupled with an event handler.

    The reason you are getting deadlocks is that the default TaskFactory will try to marshal interupt callbacks back to the SynchronizationContext, which is likely your UI thread.

    Even if you use Task.ConfigureAwait(false) there is no guarantee that further down the callstack you don't have a callback which expects the UI thread.

    As long as you block the SynchronizationContext thread, there is a very high possibility that you will deadlock.

    It is also worth noting that it is possible that the asynchronous code seems to sometimes work. This is because, an async method that returns a Task, is allowed to synchronously complete (for example Task.Return<T>(T result)). This will often happen with methods that have a cache (like HttpRequests).

    EDIT: @SriramSakthivel suggest that you can run an async method synchronously by wrapping it within Task.Run. This is because Task.Run will run the code without the parent SynchronizationContext.

    Task.Run(RunRequest).Result;

    I personally do not recommend this, as it relies on the specific implementation of Task.Run along with TaskFactory to work. It is entirely possible (but unlikely) that a new version of .net will break this piece of code.

    0 讨论(0)
提交回复
热议问题