3

It's been 4 weeks since I dived into C# programming. It's really fun, however, I got a pain in the ass:

When I start a Task with HttpClient.PostAsync() alone, it works fine. But if I continue with something else, the orginal Task will be canceled, not by me. Looks like the Task is not happy about being continued.

Task<HttpResponseMessage> task0;
Task task1;

using (var client = new HttpClient())
{
    HttpContent content = new ByteArrayContent(new byte[]{});

    task0 = client.PostAsync("<valid http address>", content);

    task1 = task0.ContinueWith((t) =>
    {
         // Do nothing
    });
}

task1.Wait();

// I got task0.IsCanceled == true here

I tried:

1, Add task0.wait() immediately after PostAsync() will solve the issue but it's not what I want. Because I need the performance benefit from async and doing that will make it totally sync.

2, Add task0.wait() before task1.wait() will cause a TaskCanceledExcpetion.

3, Remove task1 and wait on task0 will be OK.

4, Call task0.start() will got "Start may not be called on a promise-style task."

So, plz someone tell me what am I doing wrong?

PS:

Before I asked this question, I had googled it for days. And some questions from StackOverflow might look relevent, but it turned out they were not the same to mine.

Who canceled my Task? She/He was asking why the continuation task wasn't executed.

Why does TaskCanceledException occur? She/He was messing up with the CancellationToken which I never did and got unexpected result.

I've also read this Task Cancellation and still got no clue.

Community
  • 1
  • 1
Haowei
  • 29
  • 4
  • I have edited your title. Please see, "[Should questions include “tags” in their titles?](http://meta.stackexchange.com/questions/19190/)", where the consensus is "no, they should not". – John Saunders Apr 14 '15 at 16:12

2 Answers2

7

Your HttpClient most likely get's disposed before PostAsync is finished. Remove using statement for test purposes, and everything will work as expected. You should dispose your client at a different point, when request is finished.

Also, many would recommend reuse single instance of HttpClient as much as possible, if your application logic allows it.

Yura
  • 2,013
  • 19
  • 25
  • Works for me! When I first saw the "using" statement I thought it's some sort of best practice when using a http client, but don't really understand what it means. I should pay more attention on the grammer detail especially when learning a different language. – Haowei Apr 14 '15 at 16:44
  • Talking about reusing HttpClient, actually I need some sort of connection pool for this purpose, because my application is designed to have high performance. But all I found in C# was ADO.NET which was special for SQL Server or other SQL databases. I need a general-purpose connection pool, and I have to implement it with ConcurrentBag of HttpClient. Is it the best way to do it? I don't know. – Haowei Apr 14 '15 at 17:53
  • HttpClient creates connection, when calling PostAsync/GetAsync and etc. methods. I don't think it can actually cache any connection, because it doen't really make sense. If you make request to any http address - you receive response and you are done. There is nothing to cache. – Yura Apr 19 '15 at 21:26
  • What does the `KeepAlive` option do if there is no caching? – Haowei May 19 '15 at 14:22
5

So, this code appears to be disposing the HttpClient as soon as you hook up the continuation, which is most likely not what you want.

If you want to use a using statement to dispose your client instance, you need to use the async and await keywords. The following code is equivalent to your example, with the compiler hooking up the continuation for you.

public async Task FooAsync()
{
    using (var client = new HttpClient())
    {
        HttpContent content = new ByteArrayContent(new byte[]{});

        await client.PostAsync("<valid http address>", content);

        // Continue your code here.
    }
}

If you want to continue using continuations created without the compiler's help, you can put the disposing logic in a continuation:

Task<HttpResponseMessage> task0;
Task task1;

var client = new HttpClient();

HttpContent content = new ByteArrayContent(new byte[]{});

task0 = client.PostAsync("<valid http address>", content);

task1 = task0.ContinueWith((t) =>
{
    client.Dispose();
})

task1.Wait();
Paul Turner
  • 38,949
  • 15
  • 102
  • 166
  • Thanks and I had tried your examples. Really helped. Maybe I should change the title of this question because it's not really about a task had been canceled for an unknown reason. It's about a task will be canceled when HttpClient was disposed. – Haowei Apr 14 '15 at 17:05
  • 1
    The question's title is fine - it reflects the symptoms, so other people with the same symptoms will find it, regardless of the root cause. If one of the answers provided works for you, be sure to mark it as the accepted answer. – Paul Turner Apr 14 '15 at 21:04