Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Task.WaitAll not waiting for task to complete

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)

like image 579
cjk84 Avatar asked Aug 22 '15 12:08

cjk84


People also ask

Does task WaitAll start the tasks?

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.

What is task WaitAll in C#?

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.

Can you tell difference between task WhenAll and task WhenAny?

WhenAll returns control after all tasks are completed, while WhenAny returns control as soon as a single task is completed.

Is task WhenAll parallel?

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.


1 Answers

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);
like image 89
i3arnon Avatar answered Sep 30 '22 10:09

i3arnon