Home > Mobile >  C# HttpClient PostAsync blocks forever
C# HttpClient PostAsync blocks forever

Time:01-17

I have a strange behavior that I can't manage to explain. In an async function, an awaited call blocks forever.

Note: it seams that the problem occurs since I moved from a console app to a Windows Form. (the call is called from the constructor of Form1(). _client is the HttpClient dotnet class.

 public async Task GetConfigurationFile()
 {
       var stringContent = new StringContent(JsonConvert.SerializeObject(companyKey), Encoding.UTF8, "application/json");
       HttpResponseMessage response = null;

       // This call works and returns the respons after a few milliseconds
       response = _client.PostAsync(_configurationFileEndpoint, stringContent).Result;

       // The same awaited call block forever and never returns.
       response = await _client.PostAsync(_configurationFileEndpoint, stringContent);           
 }

  public Form1()
  {
        InitializeComponent();
        _engine = new Engine();
  }

 public Engine()
    {
        // Logic similar to this.
        Configuration configuration = null;
        try
        {
            using (var reader = new StreamReader(Directory.GetCurrentDirectory()   "/configuration.json"))
            {
                configuration = Newtonsoft.Json.JsonConvert.DeserializeObject<Configuration>(reader.ReadToEnd());
            }
        }
        catch (Exception ex)
        { 
               // Something done
        }
        _apiCall = new PlatformCommunication(configuration);

        if (configuration == null)
        {               
            try
            {
                _apiCall.GetConfigurationFile().Wait();
            }
            catch (Exception exc)
            {

            }
        }
    }

CodePudding user response:

You are doing this:

_apiCall.GetConfigurationFile().Wait();

As explained in many places, such as here - blocking on async code from UI thread is bad idea. When you have this:

response = await _client.PostAsync(_configurationFileEndpoint, stringContent); 

the SynchronizationContext will be captured before await and execution after await will continue on that context, which means in this case on UI thread. It cannot continue there, because UI thread is blocked by GetConfigurationFile().Wait(), so you have deadlock.

When you have this:

response = _client.PostAsync(_configurationFileEndpoint, stringContent).Result;

The code inside PostAsync uses ConfigureAwait(false) on every async call, to prevent continuations running on captured context. So all continuations run on thread pool threads and you can get away with blocking on async call with Result in this case (doesn't make it good idea still). Then after this change your GetConfigurationFile becomes synchronous (there is no await left), so you can get away with Wait() also.

You can do the same ConfigureAwait(false):

response = await _client.PostAsync(_configurationFileEndpoint, stringContent).ConfigureAwait(false);

And it will help in this case, but that's not the way to solve this problem. The real way is to just not block on async code on UI thread. Move _apiCall.GetConfigurationFile() outside of constructor.

CodePudding user response:

@YK1: to prevent blocking calls, I can move the code in the constructor of Engine() to an Async Initialize function and await _apiCall.GetConfigurationFile() instead of_apiCall.GetConfigurationFile().Wait(); But then in my Winform, I need to await engine.Initialize() from an Async function which I don't have? ( engine must run automatically, not be behind a start button), reason why I put it in the constructor of the form which is not async.

Instead of constructor, move your startup code code to an async method. You can subscribe to Form_Load event and call that method.

class Engine 
{

    public async Task Init()
    {
        // Logic similar to this.
        Configuration configuration = null;
        try
        {
            using (var reader = new StreamReader(Directory.GetCurrentDirectory()   "/configuration.json"))
            {
                configuration = Newtonsoft.Json.JsonConvert.DeserializeObject<Configuration>(reader.ReadToEnd());
            }
        }
        catch (Exception ex)
        { 
               // Something done
        }
        _apiCall = new PlatformCommunication(configuration);

        if (configuration == null)
        {               
            try
            {
                await _apiCall.GetConfigurationFile();
            }
            catch (Exception exc)
            {

            }
        }
    }
}

and

private async void Form_Load(object sender, EventArgs e)
{
    _engine = new Engine();
    await _engine.Init();
}
  • Related