1

So I have a method that chains tasks together to do work

var tasks = new List<Task>();

tasks.Add(DoWorkAsync(1));
tasks.Add(DoWorkAsync(2));
tasks.Add(DoWorkAsync(3));
tasks.Add(DoWorkAsync(4));
tasks.Add(DoWorkAsync(5));
tasks.Add(DoWorkAsync(6));

await Task.WhenAll(tasks.ToArray());

To get better performance/responsiveness out of this method I was looking at using ConfigureAwait(false)

The above method does not have any need to run on the same synchronisation context as the calling thread.

what is the correct way to use ConfigueAwait(false) when chaining Tasks and do I need to use ConfigureAwait for each individual task and/or do i need to use it on Task.WhenAll

As an extra question...

Does ConfigureAwait actually do anything (or improve performance/responsiveness) if there is no code that runs after the await?

JKennedy
  • 18,150
  • 17
  • 114
  • 198
  • is the number of tasks fixed? or does it vary? – Marc Gravell Jul 03 '19 at 13:18
  • 1
    As far as I'm aware, you only need to configure when you need to await, thus you can do it on `Task.WhenAll`. – Kieran Devlin Jul 03 '19 at 13:19
  • @MarcGravell it varies – JKennedy Jul 03 '19 at 13:19
  • @user1 please see [this](https://stackoverflow.com/a/30671005/1797425) answer by Scott Chamberlain which shows the `correct way` to use `ConfigureAwait`... – Trevor Jul 03 '19 at 13:20
  • 1
    You would *expect* the internal implementation of `Task.WhenAll` to disable sync-context since *it doesn't need it either*, so you *should* just have to use `await Task.WhenAll(tasks).ConfigureAwait(false);` (note I removed the `ToArray` - you usually don't need it). However, you could check the github source if unsure... – Marc Gravell Jul 03 '19 at 13:23
  • 3
    @Pac0 What is aggressive about downvotes? Don't take it so personally. – DavidG Jul 03 '19 at 13:25
  • @MarcGravell from my understanding if you ran `await Task.WhenAll` on the UI Thread then you would expect it to marshall the code after the await back to the UI Thread, which is where my thought of using `ConfigureAwait` came from. Although I could be completely wrong. You can see my confusion – JKennedy Jul 03 '19 at 13:28
  • @Pac0 `but a flock of downvotes was coming in, in less than a few seconds. So... deleted`, maybe the downvoters were typing a response as to why they downvoted? – Trevor Jul 03 '19 at 13:29
  • 1
    @user1 lol, I actually forgot the important bit! have edited the comment, that's my bad sorry – Marc Gravell Jul 03 '19 at 13:32
  • @Pac0 FWIW, I think your answer was fine and correct; I was trying to upvote it when it died – Marc Gravell Jul 03 '19 at 13:33
  • @MarcGravell That makes more sense. Thanks! One additional question.. I'm guessing `ConfigureAwait` use applicable regardless of whether code actually runs after the await or not? I looked at the source code for `Task.WhenAll` but its far too complicated for me so I will assume that it already uses `ConfigureAwait` on the task list – JKennedy Jul 03 '19 at 13:36
  • Downvotes are rarely aggressive. In this case it's not clear why one person who knows something about this was about to upvote and three others quickly downvoted. Not a big deal, but clearly there's a little room all around to improve communication. – Scott Hannen Jul 03 '19 at 13:44

1 Answers1

3

Ok so from the comments and chaining a few other questions together I think I have a better understanding of when to use configure await...

All of the below suggestions presume your task does not need to return to the calling thread. i.e. you have a valid ConfigureAwait use case

  1. ConfigureAwait configures the await, not the Task - You only need to use ConfigureAwait on the line where await is used

    From this answer: Using ConfigureAwait(false) on tasks passed in to Task.WhenAll() fails

  2. You do need to use ConfigureAwait even if it is the last line in the method

    From This answer: Does Task.ConfigureAwait(false) on the last method line affect anything?

  3. ConfigureAwait doesn't need to be called when using ContinueWith

    From this answer: ConfigureAwait(false) when using ContinueWith

  4. Tasks contained in Task.WhenAll are already configured with ConfigureAwait

    Also from this answer: Using ConfigureAwait(false) on tasks passed in to Task.WhenAll() fails

Which leaves me with my code looking like so:

var tasks = new List<Task>();

tasks.Add(DoWorkAsync(1));
tasks.Add(DoWorkAsync(2));
tasks.Add(DoWorkAsync(3));
tasks.Add(DoWorkAsync(4));
tasks.Add(DoWorkAsync(5));
tasks.Add(DoWorkAsync(6));

await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false);

To my surprise it seems that using ConfigureAwait is advised by most of the community, although when talking about Threading/async/await this never really gets a mention.

Extra information: Why would I bother to use Task.ConfigureAwait(continueOnCapturedContext: false);

I'm not an expert in this field I've just linked together relevent other questions, so if I've missed anything / got anything wrong, feel free to chip in.

Community
  • 1
  • 1
JKennedy
  • 18,150
  • 17
  • 114
  • 198
  • I would phrase (1) as "`ConfigureAwait` configures the `await`, not the `Task`". Ignore `ContinueWith`; it's a low-level, dangerous operation that should almost never be used in modern code. – Stephen Cleary Jul 03 '19 at 14:41
  • Thanks @StephenCleary. I'm interrested in finding out more about avoiding `ContinueWith`. Have you got any links with information about the problems with it/ avoiding it / alternatives / any valid use cases – JKennedy Jul 03 '19 at 14:45
  • The best alternative for `ContinueWith` is `await`. To use properly, you [always have to pass a `TaskScheduler`](https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html). Finally, it's just plain awkward to pass a delegate continuation rather than just continuing the `async` method. – Stephen Cleary Jul 03 '19 at 15:14