ASP.NET Web API Client ProgressMessageHandler Post Task stuck in WinForm App

 

Questions


I’m using HttpClient and ProgressMessageHandler from the MS ASP.NET Web API Client Libraries.

I’ve happily tinkered with this in a console application without issue, but now in a WinForm app, a “Post” task just gets plain old stuck on either .Wait() or .Result.

Below is a complete listing of my very simple test application. Button 1 works fine, Button 2 freezes every time on the call to postTask.Result. Why?

Targetting 4.0 or 4.5 makes no difference. The same code in a console application has no issues.

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Handlers;
using System.Windows.Forms;

namespace WindowsFormsApplication13
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private ProgressMessageHandler _progressHandler = new ProgressMessageHandler();
        private const string SERVICE_URL = "http://www.google.com.au";

        private HttpClient GetClient(bool includeProgressHandler = false)
        {
            var handlers = new List<DelegatingHandler>();

            if (includeProgressHandler)
            {
                handlers.Add(_progressHandler);
            }

            var client = HttpClientFactory.Create(handlers.ToArray());
            client.BaseAddress = new Uri(SERVICE_URL);
            return client;
        }

        private void PostUsingClient(HttpClient client)
        {
            var postTask = client.PostAsJsonAsync("test", new
            {
                Foo = "Bar"
            });

            var postResult = postTask.Result;

            MessageBox.Show("OK");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            using (var client = GetClient())
            {
                PostUsingClient(client);
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            using (var client = GetClient(true))
            {
                PostUsingClient(client);
            }
        }
    }
}

Update

OK, so it looks like this is my issue. For .NET 4.5, the obvious solution would be, as @StephenCleary suggests, to let the async / await pattern permeate from the PostAsJsonAsync call all the way down into the button-click handlers. Something more like this:

private Task<HttpResponseMessage> PostUsingClient(HttpClient client)
{
    return client.PostAsJsonAsync("test", new
    {
        Foo = "Bar"
    });
}

private async void button2_Click(object sender, EventArgs e)
{
    var client = GetClient(true);
    var response = await PostUsingClient(client);
}

Now my problem is getting an equivalent solution in .NET 4.0 (for legacy reasons, of course). A close approximation would be to use a continuation in the button-click handler, trouble is (again for legacy reasons), I actually want the button-click handler to block until the async returns. I’ve found some creative solutions using the 4.0 compatible yield operator, but they feel a little messy. Instead, the simplest alternative I can devise is:

private void button2_Click(object sender, EventArgs e)
{
    var result = Task.Run(() => { return PostUsingClient(client); }).Result;
}

I can’t imagine this is the most performant implementation, and it still feels frankly clumsy. Can I do better?

 

 

————————————————-

Answer

Any time you try to mix synchronous and asynchronous code, you’re going to end up with a bit of a mess. (I have a blog post explaining why this deadlock happens on Windows Forms but not Console apps).

The best solution is to make it all async.

(again for legacy reasons), I actually want the button-click handler to block until the async returns.

Consider some alternatives. Would it be possible to disable the button at the beginning of the async click handler and re-enable it at the end? This is a fairly common approach.

Perhaps if you describe why you want the button-click handler to block (in another question), we could suggest alternative solutions.

the simplest alternative I can devise is [using Task.Run.] I can’t imagine this is the most performant implementation, and it still feels frankly clumsy.

It’s as good a solution as any other. It is possible to directly call Result if all your awaits in the call hierarchy of PostUsingClient use ConfigureAwait(false). One problem with Result is that it will wrap any exceptions in AggregateException, so your error handling becomes more complex, too.

asp.net-web-api,c++,task

Facebook Comments

Post a comment