James Ko
James Ko

Reputation: 34629

TaskCanceledException when not awaiting

I seem to be getting a TaskCanceledException whenever I return another Task synchronously instead of awaiting it, following the guidelines in When at last you await.

TaskCanceledException code

public static class Download
{
    public static Task<byte[]> FromYouTubeAsync(string videoUri)
    {
        using (var client = new HttpClient())
        {
            return FromYouTubeAsync(
                () => client
                .GetStringAsync(videoUri),
                uri => client
                .GetByteArrayAsync(uri));
        }
    }

    public async static Task<byte[]> FromYouTubeAsync(
        Func<Task<string>> sourceFactory, Func<string, Task<byte[]>> downloadFactory)
    {
        string source = await // TaskCanceledException here
            sourceFactory()
            .ConfigureAwait(false);

        // find links

        return await
            downloadFactory(links.First())
            .ConfigureAwait(false);
    }
}

Exception-free code

Here, the first overload of the method's signature is changed to async, and it awaits the second overload. For some reason, this prevents the TaskCanceledException.

public static class Download
{
    public async static Task<byte[]> FromYouTubeAsync(string videoUri)
    {
        using (var client = new HttpClient())
        {
            return await FromYouTubeAsync(
                () => client
                .GetStringAsync(videoUri),
                uri => client
                .GetByteArrayAsync(uri));
        }
    }

    public async static Task<byte[]> FromYouTubeAsync(
        Func<Task<string>> sourceFactory, Func<string, Task<byte[]>> downloadFactory)
    {
        string source = await // No exception!
            sourceFactory()
            .ConfigureAwait(false);

        // find links

        return await
            downloadFactory(links.First())
            .ConfigureAwait(false);
    }
}

Why does this happen and what can I do to fix it (besides awaiting the method, which wastes resources as described in the link above)?

Upvotes: 2

Views: 1073

Answers (1)

Stephen Cleary
Stephen Cleary

Reputation: 457402

Sorry, the link you posted is about applying an optimization which is only applicable if the method does nothing after its await. To quote the post:

In this case, however, we’re being handed a task to represent the last statement in the method, and thus it’s in effect already a representation of the entire method’s processing...

In your example, the task does not represent the last statement in the method. Look again:

public async static Task<byte[]> FromYouTubeAsync(string videoUri)
{
  using (var client = new HttpClient())
  {
    return await FromYouTubeAsync(...);
  }
}

There's something happening after the await: specifically, the disposing of client. So the optimization mentioned in that blog post does not apply here.

This is why you're seeing an exception if you try to return the task directly:

public static Task<byte[]> FromYouTubeAsync(string videoUri)
{
  using (var client = new HttpClient())
  {
    return FromYouTubeAsync(...);
  }
}

This code is starting the download, then disposing the HttpClient, and then returning the task. HttpClient will cancel any outstanding operations when it is disposed.

The code using await will (asynchronously) wait for the HTTP operation to complete before it disposes the HttpClient. That is the behavior you need, and await is the cleanest way to express it. In this case, it's not a "waste of resources" at all, because you have to defer disposing until after the download completes.

Upvotes: 7

Related Questions