Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Task.WaitAll - Not Waiting?

I am trying to get my head around Task.WaitAll(..) in the Task Parallel Library.

I am running a test using the following code to try to simulate two tasks, one that will run longer (10 seconds) than the specified amount of time to wait for (10,, and one to run less (3 seconds) than the specified amount of time to wait for. The specified amount of time is 5 seconds. The code I am using is the following:

Task<int>[] tasks = new Task<int>[]
{
    Task.Factory.StartNew<int>(()=>
        {
            Thread.Sleep(10000);
            return 1;
        }),
    Task.Factory.StartNew<int>(()=>
        {
            Thread.Sleep(3000);
            return 2;
        })
};

TimeSpan timeSpan = new TimeSpan(0, 0, 5);
Task.WaitAll(tasks,timeSpan);

int[] results = new int[tasks.Length];
for(int i = 0; i < tasks.Length;i++)
{
    Console.WriteLine(tasks[i].Result);
}

As far as the timeSpan, I have also tried directly passing in 5000 instead of the timeSpan variable, but it is not working. I am getting the following output:

1
2

I would expect to only get the following output because the other thread is executing longer than the expected amount of time to wait.

1

Am I misunderstanding this, or doing this test wrong?

like image 939
TheJediCowboy Avatar asked Dec 09 '22 21:12

TheJediCowboy


2 Answers

While Task.Delay is what you'd generally want to use instead (so you're not wasting the thread during the sleep), your issue actually isn't related to that.

What's happening here is that when you go to print out the results, you're accessing the Result property of each task. That blocks on the task completing, so you're waiting 5 seconds in your WaitAll, then another 5 seconds when you print out the result of the 10 second task.

Based on your stated intention, you need to check the task status before accessing the Result, since your intent is not to block on it, but only to print it out if it happens to have already finished:

int[] results = new int[tasks.Length];
for (int i = 0; i < tasks.Length; i++)
{
    if (tasks[i].IsCompleted)
    {
        Console.WriteLine(tasks[i].Result);
    }
}

BTW, you can show that the 'block on Result' is what's happening in the original code by just adding some simple time checks (for instance, using Stopwatch, like this)

Task<int>[] tasks = new Task<int>[]
{
    Task.Factory.StartNew<int>(()=>
        {
            Thread.Sleep(10000);
            return 1;
        }),
    Task.Factory.StartNew<int>(()=>
        {
            Thread.Sleep(3000);
            return 2;
        })
};

TimeSpan timeSpan = new TimeSpan(0, 0, 5);
var stopwatch = Stopwatch.StartNew();
Task.WaitAll(tasks, timeSpan);
Console.WriteLine("WaitAll took {0} seconds", stopwatch.Elapsed.TotalSeconds);

int[] results = new int[tasks.Length];
for (int i = 0; i < tasks.Length; i++)
{
    stopwatch = Stopwatch.StartNew();
    Console.WriteLine(tasks[i].Result);
    Console.WriteLine("Printing result took {0} seconds", stopwatch.Elapsed.TotalSeconds);
}

This yields console output of:

WaitAll took 4.9996961 seconds
1
Printing result took 5.0050012 seconds
2
Printing result took 0.0004338 seconds
like image 170
James Manning Avatar answered Dec 11 '22 09:12

James Manning


Do not use Thread.Sleep in tasks. The Task scheduler does not guarantee one thread per tasks and sleeping may affect other tasks (see Task is ignoring Thread.Sleep). Use Task.Delay() instead.

like image 41
akton Avatar answered Dec 11 '22 10:12

akton