While trying to figure out the new (maybe not so new now, but new to me, anyway) Task
asynchronous programming in C#, I ran into a problem that took me a bit to figure out, and I'm not sure why.
I have fixed the problem, but I am still not sure why it was a problem to begin with. I just thought I'd share my experience in case anyone out there runs into the same situation.
If any gurus would like to inform me of the cause of the problem, that'd be wonderful and much appreciated. I always like knowing just why something doesn't work!
I made a test task, as follows:
Random rng = new Random((int)DateTime.UtcNow.Ticks);
int delay = rng.Next(1500, 15000);
Task<Task<object>> testTask = Task.Factory.StartNew<Task<object>>(
async (obj) =>
{
DateTime startTime = DateTime.Now;
Console.WriteLine("{0} - Starting test task with delay of {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (int)obj);
await Task.Delay((int)obj);
Console.WriteLine("{0} - Test task finished after {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (DateTime.Now - startTime).TotalMilliseconds);
return obj;
},
delay
);
Task<Task<object>>[] tasks = new Task<Task<object>>[] { testTask };
Task.WaitAll(tasks);
Console.WriteLine("{0} - Finished waiting.", DateTime.Now.ToString("h:mm:ss.ffff"));
// make console stay open till user presses enter
Console.ReadLine();
And then I ran the application to see what it spat out. Here is some sample output:
6:06:15.5661 - Starting test task with delay of 3053ms.
6:06:15.5662 - Finished waiting.
6:06:18.5743 - Test task finished after 3063.235ms.
As you can see, the Task.WaitAll(tasks);
statement didn't do much. It waited a grand total of 1 millisecond before continuing execution.
I have answered my own "question" below - but as I said above - if anyone more knowledgeable than I would care to explain why this doesn't work, please do!
(I think it might have something to do with the execution 'stepping-out' of the method once it reaches an await
operator - then stepping back in once the awaiting is done... But I am probably wrong)
WhenAll we will get a task object that isn't complete. However, it will not block but will allow the program to execute. On the contrary, the Task. WaitAll method call actually blocks and waits for all other tasks to complete.
WaitAll(Task[], Int32) Waits for all of the provided Task objects to complete execution within a specified number of milliseconds. WaitAll(Task[], CancellationToken) Waits for all of the provided Task objects to complete execution unless the wait is cancelled.
WhenAll returns control after all tasks are completed, while WhenAny returns control as soon as a single task is completed.
WhenAll has designed to handle concurrent I/O bound Tasks with higher scalability as it uses asynchronous non-blocking way to share threads to handle concurrent requests. But, on the other hand, Parallel itself is synchronous. So it is beneficial to use it in CPU bound logics to get better performance.
You should avoid using Task.Factory.StartNew
with async-await. You should use Task.Run
instead.
An async method returns a Task<T>
, an async delegate does as well. Task.Factory.StartNew
also returns a Task<T>
, where its result is the result of the delegate parameter. So when used together it returns a Task<Task<T>>>
.
All that this Task<Task<T>>
does is execute the delegate until there's a task to return, which is when the first await is reached. If you only wait for that task to complete you aren't waiting for the whole method, just the part before the first await.
You can fix that by using Task.Unwrap
which creates a Task<T>
that represents that Task<Task<T>>>
:
Task<Task> wrapperTask = Task.Factory.StartNew(...);
Task actualTask = wrapperTask.Unwrap();
Task.WaitAll(actualTask);
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