Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing in an already cancelled CancellationToken causes HttpClient to hang

I am wanting to use a CancellationToken to cancel a call to HttpClient.PostAsJsonAsync. However, with the following setup the call to PostAsJsonAsync hangs indefinitely (I left it running for several minutes).

CancellationTokenSource source = new CancellationTokenSource();
source.Cancel();
HttpClient client = new HttpClient();

try
{
    var task = client.PostAsJsonAsync<MyObject>("http://server-address.com",
        new MyObject(), source.Token);

    task.Wait();
}
catch (Exception ex)
{
    //Never gets hit.
}

Note that I am passing an already cancelled CancellationTokenSource - I have the same problem if I cancel the token using Task.Delay with a short delay.

I realise I could simply check whether the token has been cancelled prior to the call, but even so, I have the same problem if the token gets cancelled after a short delay, i.e., it's not cancelled before the method call but becomes so shortly after it.

So my question is, what is causing this and what can I do to work around/fix it?

Edit

For those looking for a workaround, inspired by @Darrel Miller's answer, I came up with the following extension method:

public static async Task<HttpResponseMessage> PostAsJsonAsync2<T>(this HttpClient client, string requestUri, T value, CancellationToken token)
{
    var content = new ObjectContent(typeof(T), value, new JsonMediaTypeFormatter());
    await content.LoadIntoBufferAsync();

    return await client.PostAsync(requestUri, content, token);
}
like image 443
nick_w Avatar asked Apr 15 '14 05:04

nick_w


1 Answers

It definitely seems to be a bug that you hit You can work around it by constructing the HttpContent/ObjectContent object yourself, like this.

CancellationTokenSource source = new CancellationTokenSource();
source.Cancel();
HttpClient client = new HttpClient();

var content = new ObjectContent(typeof (MyObject), new MyObject(), new JsonMediaTypeFormatter());
content.LoadIntoBufferAsync().Wait();
try
{
    var task = client.PostAsync("http://server-address.com",content, source.Token);

    task.Wait();
}
catch (Exception ex)
{
    //This will get hit now with an AggregateException containing a TaskCancelledException.
}

Calling the content.LoadIntoBufferAsync forces the deserialization to happen before the PostAsync and seems to avoid the deadlock.

like image 75
Darrel Miller Avatar answered Sep 16 '22 19:09

Darrel Miller