Given the code below what is the most elegant way to notify consumers when all tasks have run to completion without blocking the thread? Currently I have a solution using a counter which is incremented before the Execute(action)
and decremented after the Continuation()
and LogException()
. If the counter is zero then it is safe to assume that no more tasks are being processed.
Asked
Active
Viewed 56 times
0

zman
- 333
- 1
- 2
- 9
-
Have you tried starting a `new Thread` that [awaits](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/await) the previous `thread`? – Jaskier Dec 13 '18 at 14:29
-
`ContinueWith` returns a task that can be passed to `Task.WhenAll` and you can then await the call to `Task.WhenAll`. – mm8 Dec 13 '18 at 15:19
1 Answers
3
First off, don't use the Task
constructor at all. If you want to run a delegate in a thread pool thread, use Task.Run
. Next, don't use ContinueWith
, use await
to add continuations to tasks. It's much easier to write correct code that way, particularly with respect to proper error handling. Your code is most effectively written as:
try
{
await Task.Run(() => Execute(action));
Continuation();
}
catch(Exception e)
{
LogExceptions(e)
}
finally
{
CustomLogging();
}

Servy
- 202,030
- 26
- 332
- 449
-
I agree with you but notice that we use custom TaskSchedulers. The main task is scheduled using an OrderedTaskScheduler and the rest (ContinueWith) are scheduled using the standard TaskSchedulers from the SynchronizationContext – zman Dec 13 '18 at 14:33
-
1@zman All of the continuations are running in the UI thread in this approach. If you want to synchronize the actual work into a queue with other tasks I'd highly suggest using something [like this](https://stackoverflow.com/a/25691829/1159478) rather than a task scheduler. But you can use `Task.Factory.StartNew` and provide a task scheduler if you *really* want to, although I wouldn't suggest it. – Servy Dec 13 '18 at 14:48
-
I was able to replace the existing code with the code above however unfortunately the `Task.Factory.StartNew` is still needed as occasionally developers swap in a "single-threaded" scheduler (i.e. the scheduler the same as the current context). Not sure this is possible in an elegant way with the approach you suggested. Also the notify approach of using a counter is still required but is only needed before the await and in the finally. – zman Dec 14 '18 at 10:02
-
@zman As I told you, all of the continuations in the code shown are *already* running in the current synchronization context. You pretty much *never* need to explicitly marshal to the current context when using `await`, because it happens *by default*. You need to explicitly indicate whenever you *don't* want code to run in the current context. Using `StartNew` to run code in the current context is a sign that you've written something wrong basically every single time. – Servy Dec 14 '18 at 14:21