Why is the original task canceled when it Continue

2019-02-18 23:13发布

问题:

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.

回答1:

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.



回答2:

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();