Waiting for a group of Tasks to complete in .NET C#

You can use the static WaitAll() method to wait for more than one task to complete. If you use the WaitAll(tasks[]) version then WaitAll only returns if all tasks in the task array have completed.

WaitAll() has a number of overloaded versions which follow the same pattern as the Wait() method we saw in this post.

Example:

Build the cancellation token:

CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;

Construct two – or more- tasks:

Task taskOne = Task.Factory.StartNew(() =>
{
	for (int i = 0; i < 10; i++)
	{
		cancellationToken.ThrowIfCancellationRequested();
		Console.WriteLine(i);
		cancellationToken.WaitHandle.WaitOne(1000);
	}
	Console.WriteLine("Task 1 complete");
}, cancellationToken);

Task taskTwo = Task.Factory.StartNew(() =>
{
	for (int i = 0; i < 5; i++)
	{
		cancellationToken.ThrowIfCancellationRequested();
		Console.WriteLine(i);
		cancellationToken.WaitHandle.WaitOne(500);
	}
	Console.WriteLine("Task 2 complete");
}, cancellationToken);

Wait for them:

Task.WaitAll(taskOne, taskTwo);

Note that a task provided to the WaitAll method is considered “complete” if either of the following is true:

  • The task finishes its work
  • The task is cancelled
  • The task has thrown an exception

If one of the tasks throws an exception then WaitAll will also throw one.

View the list of posts on the Task Parallel Library here.

Wait for a single Task in .NET C#: the Wait() method

Waiting for a single Task to finish is as easy as calling the Wait() method or one of its overloaded versions:

Wait();
Wait(CancellationToken);
Wait(int);
Wait(TimeSpan);
Wait(int, CancellationToken);

In short from top to bottom:

  • Wait until the Task completes, is cancelled or throws an exception
  • Wait until the cancellation token is cancelled or the task completes, is cancelled or throws an exception
  • Wait for ‘int’ amount of milliseconds to pass OR for the task to complete, be cancelled or throw an exception, whichever happens first
  • Wait for ‘TimeSpan’ amount of time to pass OR for the task to complete, be cancelled or throw an exception, whichever happens first
  • Wait for ‘int’ amount of milliseconds to pass, for the cancellation token to be cancelled, OR for the task to complete, be cancelled or throw an exception, whichever happens first

Example:

Construct the cancellation token:

CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;

Start a task:

Task task = Task.Factory.StartNew(() =>
{
	for (int i = 0; i < 10; i++)
	{
		cancellationToken.ThrowIfCancellationRequested();
		Console.WriteLine(i);
		cancellationToken.WaitHandle.WaitOne(1000);
	}
}, cancellationToken);

We can then wait for the task to complete:

task.Wait();

If we are impatient then we specify a maximum amount of milliseconds for the task to complete otherwise it is abandoned:

task.Wait(2000);

The other overloads work in a similar manner.

View the list of posts on the Task Parallel Library here.

Suspending a Task using SpinWait in .NET C#

You may want to put a Task to sleep for some time. You might want to check the state of an object before continuing. The Task continues after the sleep time.

One way to solve this is using the Thread.SpinWait method since the Task library uses .NET threading support in the background.

First construct your cancellation token:

CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;

Use this token in the constructor of the task:

Task task = Task.Factory.StartNew(() =>
{
	for (int i = 0; i < 100000; i++)
	{
		Thread.SpinWait(10000);
		Console.WriteLine(i);
		cancellationToken.ThrowIfCancellationRequested();
	}
}, cancellationToken);

You can use the cancellation token to interrupt the task:

cancellationTokenSource.Cancel();

We looked at Thread.Sleep and the WaitOne method in the previous posts on TPL. With those two techniques the thread that’s currently performing the task gives up its turn while sleeping. This gives a chance to other threads to do something. With spin waiting it’s different as the thread does not give up its turn.

The integer argument in SpinWait indicates an iteration. It is the number of times that a tight CPU loop will be performed. The exact amount of sleep time will depend on the speed of the CPU so it’s not some well defined period of time. Therefore spin waiting doesn’t stop using the CPU.

Bear in mind these limitations and use SpinWait with care – if ever.

View the list of posts on the Task Parallel Library here.

Cancel multiple Tasks at once in .NET C#

You cannot directly interrupt a Task in .NET while it’s running. You can do it indirectly through the CancellationTokenSource object. This object has a CancellationToken property which must be passed into the constructor of the Task:

CancellationTokenSource cancellationTokenSource	= new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;

You can re-use the same token in the constructor or several tasks:

Task firstTask = Task.Factory.StartNew(() =>
{
	for (int i = 0; i < 100000; i++)
	{
		cancellationToken.ThrowIfCancellationRequested();
		Console.WriteLine(i);
	}
}, cancellationToken);

Task secondTask = Task.Factory.StartNew(() =>
{
	for (int i = 0; i < int.MaxValue; i++)
	{
		cancellationToken.ThrowIfCancellationRequested();
		Console.WriteLine(i);
	}
}, cancellationToken);

We simply count up to 100000 in the Task bodies. We’re using the ThrowIfCancellationRequested() method of the cancellation token, I’ll explain shortly what it does.

You can cancel both tasks by calling the Cancel() method of the token like this:

cancellationTokenSource.Cancel();

Note that this method only signals the wish to cancel a task. .NET will not actively interrupt the task, you’ll have to monitor the status through the IsCancellationRequested property. It is your responsibility to stop the task. In this example we’ve used the built-in “convenience” method ThrowIfCancellationRequested() which throws an OperationCanceledException which is a must in order to correctly acknowledge the cancellation. If you forget this step then the task status will not be set correctly. Once the task has been requested the stop it cannot be restarted.

If you need to do something more substantial after cancellation, such as a resource cleanup, then you can monitor cancellation through the IsCancellationRequested and throw the OperationCanceledException yourself:

for (int i = 0; i < 100000; i++)
{
	if (token.IsCancellationRequested)
	{
		Console.WriteLine("Task cancellation requested");
		throw new OperationCanceledException(token);
	}
	else
	{
		Console.WriteLine(i);
	}
}

View the list of posts on the Task Parallel Library here.

Monitoring Task cancellation in .NET C# using a Wait handle

You cannot directly interrupt a Task in .NET while it’s running. You can do it indirectly through the CancellationTokenSource object. This object has a CancellationToken property which must be passed into the constructor of the Task:

CancellationTokenSource cancellationTokenSource	= new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;

The cancellation token can be used as follows:

// create the task
Task task = Task.Factory.StartNew(() =>
{
	for (int i = 0; i < 100000; i++)
	{
		if (cancellationToken.IsCancellationRequested)
		{
			Console.WriteLine("Task cancelled, must throw exception.");
			throw new OperationCanceledException(cancellationToken);
		}
		else
		{
			Console.WriteLine(i);
		}
	}
}, cancellationToken);

We simply count up to 100000 in the Task body. Note the IsCancellationRequested property of the token. We monitor within the loop whether the task has been cancelled.

The cancellation token has a property called WaitHandle which has a method called WaitOne(). This method blocks until the Cancel() method is called on the token source provided in the Task constructor. It can be used by setting up another Task which calls the WaitOne method which blocks until the first Task has been cancelled:

Task secondTask = Task.Factory.StartNew(() =>
	{
		cancellationToken.WaitHandle.WaitOne();
		Console.WriteLine("WaitOne called.");
	});

You can cancel the task by calling the Cancel() method of the token like this:

cancellationTokenSource.Cancel();

Note that this method only signals the wish to cancel a task. .NET will not actively interrupt the task, you’ll have to monitor the status through the IsCancellationRequested property. It is your responsibility to stop the task. In this example we throw an OperationCanceledException which is a must in order to correctly acknowledge the cancellation. If you forget this step then the task status will not be set correctly. Once the task has been requested the stop it cannot be restarted.

If that’s all you want to do, i.e. throw an OperationCanceledException, then there’s a shorter version:

cancellationToken.ThrowIfCancellationRequested();

This will perform the cancellation check and throw the exception in one step. The loop can thus be simplified as follows:

Task task = Task.Factory.StartNew(() =>
{
	for (int i = 0; i < 100000; i++)
	{
		//shorthand
        	cancellationToken.ThrowIfCancellationRequested();

		Console.WriteLine(i);
	}
}, cancellationToken);

View the list of posts on the Task Parallel Library here.

Monitoring Task cancellation in .NET C# using a delegate

You cannot directly interrupt a Task in .NET while it’s running. You can do it indirectly through the CancellationTokenSource object. This object has a CancellationToken property which must be passed into the constructor of the Task:

CancellationTokenSource cancellationTokenSource	= new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;

The cancellation token can be used as follows:

// create the task
Task task = Task.Factory.StartNew(() =>
{
	for (int i = 0; i < 100000; i++)
	{
		if (cancellationToken.IsCancellationRequested)
		{
			Console.WriteLine("Task cancelled, must throw exception.");
			throw new OperationCanceledException(cancellationToken);
		}
		else
		{
			Console.WriteLine(i);
		}
	}
}, cancellationToken);

We simply count up to 100000 in the Task body. Note the IsCancellationRequested property of the token. We monitor within the loop whether the task has been cancelled.

You can register a delegate on the token that should be called when the task is cancelled:

cancellationToken.Register(() =>
{
	DoSomethingUponTaskCancellation();
});

You can clean up resources, cancel other tasks that rely on this particular task, notify the user, log the cancellation etc., it’s up to you how you implement the delegate. The Register method accepts an Action object or an Action of T which allows you to provide a state parameter.

You can cancel the task by calling the Cancel() method of the token like this:

cancellationTokenSource.Cancel();

Note that this method only signals the wish to cancel a task. .NET will not actively interrupt the task, you’ll have to monitor the status through the IsCancellationRequested property. It is your responsibility to stop the task. In this example we throw an OperationCanceledException which is a must in order to correctly acknowledge the cancellation. If you forget this step then the task status will not be set correctly. Once the task has been requested the stop it cannot be restarted.

If that’s all you want to do, i.e. throw an OperationCanceledException, then there’s a shorter version:

cancellationToken.ThrowIfCancellationRequested();

This will perform the cancellation check and throw the exception in one step. The loop can thus be simplified as follows:

Task task = Task.Factory.StartNew(() =>
{
	for (int i = 0; i < 100000; i++)
	{
		//shorthand
        	cancellationToken.ThrowIfCancellationRequested();

		Console.WriteLine(i);
	}
}, cancellationToken);

View the list of posts on the Task Parallel Library here.

Exception handling in the .NET Task Parallel Library with C#: a safety catch-all

In the previous posts on exception handling in Tasks (here, here, and here) we saw how to handle exceptions thrown by tasks. We saw that unhandled aggregate exceptions will be re-thrown by the default escalation policy. This will lead your application to be terminated immediately. If there are other ongoing tasks in that moment then those will be terminated too.

There can be situations that your application uses threads to such an extent that you either cannot put a try-catch block around every single Task.Wait, Task.WaitAll etc. calls or you simply forget it. There is a way to subscribe to all unhandled aggregate exceptions that the task scheduler encounters. You can then decide what to do with the exception there.

The task scheduler has an UnobservedTaskException event. It’s straightforward to subscribe to that even with the += operator.

TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;

…where the event handler looks like this:

static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
	e.SetObserved();
	((AggregateException)e.Exception).Handle(ex =>
	{
		Console.WriteLine("Exception type: {0}", ex.GetType());
		return true;
	});
}

The SetObserved() method tells the task scheduler that the exception has been taken care of so there’s no need for it to bubble up. The UnobservedTaskExceptionEventArgs object has an Exception property that must be cast to an AggregateException. From then on you can call the Handle() method of the aggregate exception as we saw before.

Start a couple of new tasks:

Task.Factory.StartNew(() =>
{
	throw new ArgumentException();
});

Task.Factory.StartNew(() =>
{
	throw new NullReferenceException();
});

Wait a bit for the tasks to finish:

Thread.Sleep(100);

Run the code with Ctrl+F5 and… er… nothing happens really. The event handler is never triggered. You won’t see the aggregate exception type printed on the console window. What happened? The UnobservedTaskException handler will be called when the tasks with the unhandled exception have been collected by the garbage collector. As long as we are holding a reference to the two tasks the GC will not collect them, and we’ll never see the exception handler in action.

If we want to force the event handler to be fired then you can add the following two lines just below the Thread.Sleep bit:

GC.Collect();
GC.WaitForPendingFinalizers();

Run the code again and you’ll see the exception messages in the console window.

Note the following: in .NET4.5 there’s a new configuration element that specifies whether unhandled task exceptions should terminate the application or not:

<ThrowUnobservedTaskExceptions
   enabled="true|false"/>

True: terminate the process if an unhandled exception is encountered.
False (default): the exact opposite of the True case

In .NET4 the default behaviour is to terminate the process as we saw. In .NET4.5 it’s the exact opposite: unhandled exceptions still cause the UnobservedTaskException event to be raised, but the process will not be terminated by default. The exception will be silently ignored. So if you want to simulate .NET4 behaviour in a multi-threaded .NET4.5 application you’ll need to set the above mentioned configuration setting to true in the config file.

View the list of posts on the Task Parallel Library here.

Exception handling in the .NET Task Parallel Library with C#: using Handle()

In this post we saw how to catch unhandled exceptions thrown by tasks. Recall that the AggregateException object has a list of inner exceptions thrown by the tasks that are waited on.

You may want to handle each type of exception in a different way. There may be types of exception that you can handle locally and some other unexpected ones that should be propagated further up the stack trace. The AggregateException object has a Handle() method where you can specify the action taken depending on the type of the inner exception. The function should return true if the exception can be dealt with locally and false if it should be propagated upwards.

Construct two tasks using the cancellation token in the first one:

CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;

Task firstTask = Task.Factory.StartNew(() =>
{
	cancellationToken.WaitHandle.WaitOne(-1);
	throw new OperationCanceledException(cancellationToken);
}, cancellationToken);

Task secondTask = Task.Factory.StartNew(() =>
{
	throw new NullReferenceException();
});

The effect of WaitHandle.WaitOne(-1) is to wait forever until the token has been cancelled.

Cancel the token which will interrupt the first task:

cancellationTokenSource.Cancel();

Wait for the tasks, catch the AggregateException and call its Handle method:

try
{
	Task.WaitAll(firstTask, secondTask);
}
catch (AggregateException ex)
{
	ex.Handle((innerException) =>
	{
		if (innerException is OperationCanceledException)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
}

We declare that OperationCanceledException is an expected type and can be dealt with locally. All other types should be re-thrown.

If you run the above code in a console app make sure to run it without debugging (Ctrl + F5), otherwise the code execution will unnecessarily stop when the OperationCanceledException is thrown in the first task.

You’ll see that the NullReferenceException is re-thrown as we return false for all types except for the OperationCanceledException type. Test returning true instead: the exception will be “swallowed” within the Handle method.

View the list of posts on the Task Parallel Library here.

Exception handling in the .NET Task Parallel Library with C#: the basics

Handling exceptions in threading is essential. Unhandled exceptions can cause your application to exit in an unexpected and unpredictable way.

If a task throws an exception that’s not caught within the task body then the exception remains hidden at first. It bubbles up again when a trigger method is called, such as Task.Wait, Task.WaitAll, etc. The exception will then be wrapped in an AggregateException object which has an InnerException property. This inner exception will hold the actual type of exception thrown by the tasks that are waited on.

Construct and start the tasks:

Task firstTask = Task.Factory.StartNew(() =>
{
	ArgumentNullException exception = new ArgumentNullException();
	exception.Source = "First task";
	throw exception;
});
Task secondTask = Task.Factory.StartNew(() =>
{
	throw new ArgumentException();
});
Task thirdTask = Task.Factory.StartNew(() =>
{
	Console.WriteLine("Hello from the third task.");
});

Wait for all the tasks, catch the aggregate exception and analyse its inner exceptions:

try
{
	Task.WaitAll(firstTask, secondTask, thirdTask);
}
catch (AggregateException ex)
{
	foreach (Exception inner in ex.InnerExceptions)
	{
		Console.WriteLine("Exception type {0} from {1}",
			inner.GetType(), inner.Source);
	}
}

Note: if you run the above code in Debug mode in a console application then the execution will stop at each exception thrown. To simulate what’s going to happen in a deployed application make sure you run the code without debugging (Ctlr + F5).

View the list of posts on the Task Parallel Library here.

Share data between Tasks using locks in .NET C# in separate regions

In the this post we saw how to use the ‘lock’ keyword to restrict access to a single critical region. You can reuse the same lock object to synchronise access to the shared variable in separate regions. The modified version of the code in the referenced post looks as follows with two critical regions:

BankAccount account = new BankAccount();

List<Task> incrementTasks = new List<Task>();
List<Task> decrementTasks = new List<Task>();

object lockObject = new object();

for (int i = 0; i < 5; i++)
{
	incrementTasks.Add(Task.Factory.StartNew(() =>
	{
		for (int j = 0; j < 1000; j++)
		{
			lock (lockObject)
			{
				account.Balance++;
			}
		}
	}));
}

for (int i = 0; i < 5; i++)
{
	decrementTasks.Add(Task.Factory.StartNew(() =>
	{
		for (int j = 0; j < 1000; j++)
		{
			lock (lockObject)
			{
				account.Balance--;
			}
		}
	}));
}

Task.WaitAll(incrementTasks.ToArray());
Task.WaitAll(decrementTasks.ToArray());

Console.WriteLine(string.Concat( "Expected value: 0, actual value: ", account.Balance));

We have two sets of tasks: one that increases the balance and another that reduces it. By reusing the same lock object in both cases we can ensure that it’s always at most one task that has access to the shared variable.

View the list of posts on the Task Parallel Library here.

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

ARCHIVED: Bite-size insight on Cyber Security for the not too technical.