I get an IEnumerable<Task> tasks
from somewhere that I do not control. I don't know if the tasks are manually created using new Task
, Task.Run
, or if they are a result of an async method call async Task DoSomethingAsync()
.
If I do await Task.WhenAll(tasks)
, I risk hanging indefinitely because maybe one or more of the tasks are not started.
I can't do tasks.ForEach(t => t.Start())
, because then I will get an InvalidOperationException "Start may not be called on a promise-style task" if it's from an async method call (already started).
I can't do await Task.WhenAll(tasks.Select(t => Task.Run(async () => await t)))
because each t
still does not start just by awaiting it.
I assume the solution has something to do with checking each task's Status
and Start()
based on that, but I also assume that it can be tricky because that status could change at any time, right? If this is still the way to go, which statuses would be correct to check and what threading issues should I worry about?
Non working case example:
//making an IEnumerable as an example, remember I don't control this part
Task t = new Task( () => Console.WriteLine("started"));
IEnumerable<Task> tasks = new[] {t};
//here I receive the tasks
await Task.WhenAll(tasks);//waits forever because t is not started
Working case example:
//calls the async function, starting it.
Task t = DoSomethingAsync();
IEnumerable<Task> tasks = new[] {t};
//here I receive the tasks and it will complete because the task is already started
await Task.WhenAll(tasks);
async Task DoSomethingAsync() => Console.WriteLine("started");
As you probably recall, await captures information about the current thread when used with Task. Run . It does that so execution can continue on the original thread when it is done processing on the other thread.
A continuation task (also known just as a continuation) is an asynchronous task that's invoked by another task, known as the antecedent, when the antecedent finishes.
The main purpose of Task. Run() is to execute CPU-bound code in an asynchronous way. It does this by pulling a thread from the thread pool to run the method and returning a Task to represent the completion of the method.
The await operator suspends evaluation of the enclosing async method until the asynchronous operation represented by its operand completes. When the asynchronous operation completes, the await operator returns the result of the operation, if any.
If for whatever reason you cannot change the code to not return unstarted tasks, you can check Status
and start task if it has Created
status:
if (task.Status == TaskStatus.Created)
task.Start();
All other task statues indicate that task is either completed, running, or being scheduled, so you don't need to start tasks in that statuses.
Of course in theory this introduces race condition, because task can be started right between your check and Start
call, but, as correctly pointed by Servy in comments - if there ever is race condition here - that means another party (which created that task) is also trying to start it. Even if you handle exception (InvalidOperationException
) - another party is unlikely to do that, and so will get exception while trying to start their own task. So only one side (either you, or code that created that task) should be trying to start it.
That said - much better than doing this is to ensure you might never get unstarted task in the first place, because it's just bad design to return such tasks to external code, at least without explicitly indicating that (while it's for some use cases ok to use unstarted task internally).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With