SoFunction
Updated on 2025-03-01

A brief discussion on C# async await deadlock problem summary

Types of programs that may be deadlocked

1. WPF/WinForm program

2. (Excluding core) program

The principle of deadlock

A deadlock may occur when calling Wait() on a Task returned by an asynchronous method or accessing the Result property.

The following WPF code will have a deadlock:

private void Button_Click_7(object sender, RoutedEventArgs e)
    {
      Method1().Wait();
    }

    private async Task Method1()
    {
      await (100);

      ("Such code");
    }

The following mvc code will also have deadlocks:

public ActionResult Index()
    {
      string s=Method1().Result;

      return View();
    }

    private async Task<string> Method1()
    {
      await (100);

      return "hello";
    }

Taking WPF code as an example, the event processor calls Method1, gets the Task object, and then calls the Task Wait method to block the thread it is in, that is, the main thread, until the Task object "completes". If the returned Task object wants to "complete", it must execute the code after await on the main thread. The main thread has been in a blocking state for a long time, and it is waiting for the Task object to complete! So a deadlock occurs.

The same goes for mvc code.

When will it be deadlocked and how to avoid it

From the above two examples, it seems that in the WPF/WinForm/ program, calling .Result/Wait() on an asynchronous method will generate a deadlock. However, this is not the case.

If the following WPF code does not have a deadlock: (get data from the web and display it in the text box. This code is only an example, and the asynchronous event processor is the right way)

private void Button_Click_8(object sender, RoutedEventArgs e)
    {
      HttpClient httpClient = new HttpClient();
       = new Uri("/");

      string html = ("/").Result;      html = "【" + html + "】";

      (html);
    }

Extract the code to obtain the data:

private void Button_Click_8(object sender, RoutedEventArgs e)
    {
      string html = GetHtml();

      (html);
    }

    private string GetHtml()
    {
      HttpClient httpClient = new HttpClient();
       = new Uri("/");

      string html=("/").Result; html = "【" + html + "】";       return html;
    }

There is absolutely no problem, that's for sure.

GetHtml() can be written as an asynchronous method, and then change it:

private void Button_Click_8(object sender, RoutedEventArgs e)
    {
      string html = GetHtml().Result;

      (html);
    }

    private async Task<string> GetHtml()
    {
      HttpClient httpClient = new HttpClient();
       = new Uri("/");
      string html=await ("/");  
      html = "【" + html + "】";
      return html;    }

(HttpClient's GetStringAsync() method is an asynchronous method. We call it and then create our own asynchronous method in async/await. We don't "Async All the Way" first.)

Run it and the deadlock appears.

Why does it not deadlock when executing the GetStringAsync() method of HttpClient, but it will appear deadlock when executing the asynchronous method you write? Is there any special handling inside the GetStringAsync() method of HttpClient?

Take a lookmono's HttpClient source code, you can find:

All await expressions are followed by ConfigureAwait (false), such as

return await ().ConfigureAwait (false);

And byTask's msdn documentationIt can be seen that ConfigureAwait (false) will indicate that the code after await will not run on the original context (which can be understood as a thread).

Change the code of GetHtml() asynchronous method:

private void Button_Click_8(object sender, RoutedEventArgs e)
    {
      string html = GetHtml().Result;

      (html);
    }
    private async Task<string> GetHtml()
    {
      HttpClient httpClient = new HttpClient();
       = new Uri("/");
      string html=await ("/").ConfigureAwait(false);  
      html = "【" + html + "】";
      return html;    }

It can be found that the deadlock will not appear.

Analysis: After GetHtml() is called, the main thread blocks, waiting for the Task object to "complete"; HttpClient has completed the data acquisition, and the code after await is executed on another thread, so the Task object is completed. The main thread resumes execution. (Note that even if "there is no code after await", that is, write return await ("/") directly in the GetHtml() method body, you still need to add .ConfigureAwait(false))

Of course, if the event handler is asynchronous, there will be no problem even if .ConfigureAwait(false) is added:

private async void Button_Click_8(object sender, RoutedEventArgs e)
    {
      string html = await GetHtml();

      (html);
    }
    private async Task<string> GetHtml()
    {
      HttpClient httpClient = new HttpClient();
       = new Uri("/");
      string html = await ("/");
      html = "【" + html + "】";
      return html;
    }

Just imagine, if GetHtml() is placed in a separate class and made into a class library, then if there is no .ConfigureAwait(false) added, it can only be assumed that people using this class library strictly follow the asynchronous programming specifications. Once the user executes .Result on GetHtml(), deadlocks are inevitable.

If you look closely at the source code of HttpClient, you will find that its GetStringAsync() method is not a "natural" asynchronous method. It also calls its own other asynchronous methods with the await operator, and adds .ConfigureAwait(false) after each call.

So, can the deadlock of the original WPF program be solved with .ConfigureAwait(false)? Note that txtLog is a text box, and the UI control can only be accessed by the UI thread, so after adding .ConfigureAwait(false) it will report an error: "InvalidOperationException: The calling thread cannot access this object because another thread owns the object". So can it be changed to this:

private void Button_Click_7(object sender, RoutedEventArgs e)
    {
      Method1().Wait();
    }

    private async Task Method1()
    {
      await (100).ConfigureAwait(false);

      (() =&gt; {
        ("Such code");
      });
    }

Still deadlock. So, behave like to use the asynchronous event processor:

private async void Button_Click_7(object sender, RoutedEventArgs e)
    {
      await Method1();
    }

    private async Task Method1()
    {
      await (100);

      ("Such code");
    }

The above code also illustrates a problem: in the asynchronous tool method, do not write code to access UI controls, otherwise the deadlock problem cannot be avoided.

Summarize

  • Deadlocks occur when not following the asynchronous programming specification - executing Wait() or .Result on the Task object returned by the asynchronous method
  • ConfigureAwait(false) The code after specifying await does not return to the original context, which can avoid deadlocks.
  • If the code after await does not need to return to the original context execution, for example, it is just to execute Http requests, obtain and process data, then ConfigureAwait(false) can be added.
  • If you are a creator of a class library, when writing an asynchronous method, you should use ConfigureAwait(false) as much as possible to ensure that once the user of the class library blocks the asynchronous method, there will be no deadlock.
  • In asynchronous class library/tool ​​methods, code that accesses UI controls should be avoided.

Attached async/await learning materials

 C# Under the Hood: async/await The author starts by writing a "waitable" method, and then analyzes the essential code generated by the asynchronous method through decompilation tools, revealing the essence of async/await - callbacks

What happens in an async method msdn programming guide, diagramming the execution process of asynchronous methods

This is the end of this article about a brief discussion on the problem of C# async await deadlock. For more related content of C# async await deadlock, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!