0

Let's assume I have this code:

await Task1Async().ConfigureAwait(false);
await Task2ASync().ConfigureAwait(false);

Configure await allows to run on different threads than the calling one.

But if I do the following:

Task.WaitAll(
    Task1Async,
    Task2Async
);
  • Do tasks run in the same context?
  • If not, can I do an equivalent of ConfigureAwait(false) ?
Thomas
  • 10,933
  • 14
  • 65
  • 136
  • There's no need to do `Task.WaitAll` as both your tasks will have completed when you get to that call. – Sean Sep 12 '19 at 10:51
  • @Sean I think he means instead of – Tim Rutter Sep 12 '19 at 10:54
  • the two awaits work as follows: Task1Async runs in a new context and the calling thread returns. Because you have used ConfigureAwait(false) the subsequent await runs in the same context as the first await once Task1Async has completed, rather than using the captured context of the calling thread. i.e. the two awaits runs the tasks sequentially whereas the waitall runs them in parallel – Tim Rutter Sep 12 '19 at 10:57
  • My understanding is that this will run both Tasks in different contexts than the calling one, so the calling thread can keep running. The WaitAll call holds the calling thread until both tasks have completed, but I don't find information if they'll run sequentially, on the same context (due to the last of ConfigureAwait(false)), or they'll run in different threads. – Thomas Sep 12 '19 at 11:00
  • https://stackoverflow.com/questions/32119507/multiple-awaits-vs-task-waitall-equivalent gives some explanation and highlights the use of WhenAll – Tim Rutter Sep 12 '19 at 11:02
  • Possible duplicate of [Understanding what multiple configureawait(false) do in a single async method](https://stackoverflow.com/questions/49593371/understanding-what-multiple-configureawaitfalse-do-in-a-single-async-method) – Tim Rutter Sep 12 '19 at 11:04
  • @TimRutter, I didn't know about WhenAll but that's specifically what I am after. I was under the impression that during an await the current thread continues execution until the result of the task is needed; isn't that the case? – Thomas Sep 12 '19 at 11:04
  • If by the "result of the task is needed" means until the task being awaited completes , then yes. Have a read of the links I posted, they are quite informative – Tim Rutter Sep 12 '19 at 11:08
  • what I mean is: if I have var a = await MyTaskAsync().ConfigureAwait(false); would the current thread continue execution, MyTaskAsync run in a different context and the current thread only be blocked if it needs the variable 'a' and the task hasn't completed? – Thomas Sep 12 '19 at 11:09
  • Yes the current thread would continue execution and because you've used ConfigureAwait(false) the assignment of the task result to variable "a" would not happen on the calling thread which it would otherwise do. This explains that part: https://medium.com/bynder-tech/c-why-you-should-use-configureawait-false-in-your-library-code-d7837dce3d7f – Tim Rutter Sep 12 '19 at 11:11
  • so, then what is the difference between my top example and WhenAll? since they both execute the tasks on different threads and lock the caller only when the result is used, if the task is not complete; or am I missing something? – Thomas Sep 12 '19 at 11:13
  • No the await does not block the caller, thats the whole point. You await the whenall and it runs all the tasks asynchronously but the caller thread returns immediately. When all the tasks have completed it runs the code after the await. I'd have a read about await and see how it all works. – Tim Rutter Sep 12 '19 at 11:23
  • if I have a = await T1().awaiter(false), b = await T2().awaiter(false), we agree that the caller continues, T1 and T2 execute in different contexts and the caller may get halted if it depends on a, or b, because T1 or T2 has completed. Is that correct? If so, when I do WhenAll(T1, T2), what is the difference in behavior? – Thomas Sep 12 '19 at 11:47
  • There are a lot of incorrect, or misleading, or confusing uses of words related to tasks here in the question and in the comments. Can you clarify the question to be more exact as to what your question is about? As an example, your statement in the question about "Configure await allows to run on different threads than the calling one." seems to me to mean that you can configure a task to run on a different thread, but that's not what this does. This configuration has to do with notifications to the synchronization context once the tasks has completed. Can you clarify? – Lasse V. Karlsen Sep 12 '19 at 12:20
  • @LasseVågsætherKarlsen: the question was answered below – Thomas Sep 12 '19 at 12:22
  • 1
    In `await T1().awaiter(false)` or with `.ConfigureAwait(false)`, what happens is that whatever T1 is doing is not impacted at all, the question is which synchronization context is used to handle the notification that T1 has completed, and thus have to execute the code following this await. – Lasse V. Karlsen Sep 12 '19 at 12:22
  • The answer has its own problems in this regard. It mentions things like different threads, etc. whereas that is neither a guarantee or part of the picture here. – Lasse V. Karlsen Sep 12 '19 at 12:22
  • I should have precised a bit more: I have multiple save operations that have to be done, and ideally in parallel; they're kind of a fire and forget type; I don't need to wait until they're complete, I wanted them to be executed in the thread pool, not blocking the caller and not having to care about their completion – Thomas Sep 12 '19 at 12:28

2 Answers2

1
async Task TestAsync()
{
  await Task1Async().ConfigureAwait(false);
  await Task2ASync().ConfigureAwait(false);
}

Configure await allows to run on different threads than the calling one.

To clarify, ConfigureAwait(false) in your code sample has absolutely no effect on the code inside Task1Async and Task2Async. By default, await will capture its context and resume on that context; this applies to the method that the await is in - TestAsync in the code above. For more information, see my async intro.

But if I [use Task.WaitAll], Do tasks run in the same context?

Task.WaitAll is used to wait for tasks. It doesn't run tasks. The TAP pattern specifies that tasks are returned "hot" - i.e., already in progress. So when your code calls Task1Async() and gets a task back, that task is already in progress. After your code gets the task, it's too late to tell it where to run - it's already started.

If not, can I do an equivalent of ConfigureAwait(false) ?

If you mean, "can I run this code on a background thread?", then you can do that using Task.Run:

// Start Task1Async on a background thread.
var task1 = Task.Run(() => Task1Async());

// Start Task2Async on a background thread.
var task2 = Task.Run(() => Task2Async());

// Asynchronously wait for both tasks to complete.
await Task.WhenAll(task1, task2);
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
-1

Some explanation as I understand it.

If you did this:

//some code

await Task1Async();
await Task2ASync();

//some other code

It would execute like this:

  1. Calling thread (eg UI context) executes "some code" synchronously.
  2. It then launches Task1Async asynchronously which will happen on the ThreadPool context,( unless it is already complete in which case execution continues at point 4 below synchronously). The calling context is capture here if the task is not complete
  3. The current thread then returns from the function, meaning it is not blocked.
  4. When Task1Async is complete, the next line is executed in the captured context eg the UI context. This then launches Task2Async asynchronously via the threadpool context (again if already complete, execution continues synchronously)
  5. Task2Async completes and then the finally "some other code" is run again using the captured context

If you instead do this:

//some code

await Task1Async().ConfigureAwait(false);
await Task2ASync().ConfigureAwait(false);

//some other code

This is what happens:
1. Calling thread executes "some code".
2. Then executes Task1Async asynchronously (threadpool).
3. The current thread returns from the function, meaning it is not blocked. 4. When Task1Async is complete, the next line is executed NOT on the captured context but instead using the threadpool. This then launches Task2Async. 5. Task2Async completes and then the final "some other code" is run again not on the calling thread but instead using the threadpool.

In both cases the functions run sequentially but do not block the calling thread.

With Task.WaitAll, the two tasks are run sequentially using the threadpool. I.e. each task is launched in a separate thread and waited for completion. The major difference here is that the calling thread is blocked until both tasks are complete.

If you want to run the two tasks in parallel and not block the calling the thread you can await WhenAll:

await Task.WhenAll(Task1Async, Task2Async);

And finally if you have any code after the await that you want to run that does not need to be run in the UI context then you would do:

await Task.WhenAll(Task1Async, Task2Async).ConfigureAwait(false);

Side note: the reason you would want ConfigureAwait(true) or omit it is if you had for example a button click handler and wanted to update something that needed to be on the UI thread:

public void OnClick(..args..)
{
    Button.IsEnabled = false;
    await SomeTask();

    //must be on UI thread. This code is executed on the context captured by the await above.
    Button.IsEnabled = true;
}
Tim Rutter
  • 4,549
  • 3
  • 23
  • 47
  • 2
    Note that calling an `async` method using `await` doesn't necessary mean that the called method will be executed on a new thread as you write. The method might also complete synchronously, or [there will be no thread at all](https://blog.stephencleary.com/2013/11/there-is-no-thread.html). – dymanoid Sep 12 '19 at 11:53
  • I get the idea of no thread, thanks for sharing that, but how can it complete synchronously? Surely that means the calling thread is blocked. DO you have another handy article I could read and further my knowledge... – Tim Rutter Sep 12 '19 at 12:00
  • 1
    If you want to truly understand the TAP, I will recommend you to read all articles and books by Stephen Cleary (above I linked one of them). E.g. your question is explained [here](https://blog.stephencleary.com/2012/02/async-and-await.html). *The beginning of an async method is executed just like any other method. That is, it runs synchronously until it hits an “await” (or throws an exception).* So if your `async` method throws before reaching an `await`, the method will complete synchronously. – dymanoid Sep 12 '19 at 12:09
  • Ok so the method can complete synchronously if there is an exception before an await OR if all the awaited task(s) are already complete. I've amended my answer. – Tim Rutter Sep 12 '19 at 12:14
  • 1
    This answer is incorrect in some of the details. We know nothing about which thread(s) will be involved in `Task1Async` or `Task2Async`. `.ConfigureAwait(false)` configures the notification mechanism involved when the tasks complete, not which threads, if any, that the task themselves execute on. – Lasse V. Karlsen Sep 12 '19 at 12:24
  • @LasseVågsætherKarlsen feel free to edit my response. I would like to gain a better understanding of this. – Tim Rutter Sep 12 '19 at 12:27
  • 1
    OP can use `await Task.WhenAll(Task1Async(), Task2Async()).ConfigureAwait(false);`. – Lasse V. Karlsen Sep 12 '19 at 12:28
  • 1
    I think @dymanoid's comment above is the best for all of us, read the articles and books by Stephen Cleary, I certainly have to go re-read them periodically when I'm stumped. Task and asynchrony in .NET is a complex issue. – Lasse V. Karlsen Sep 12 '19 at 12:29