The Task Parallel Library and You (with special guest GTK#)

By | August 28, 2017

Introduction

A couple of years ago I did a post on the BackgroundWorker. While the BackgroundWorker class is a perfectly acceptable way to get operations done asynchronously in .Net/Mono, the Task Parallel Library is the newest and current recommended way of writing asynchronous code. So I thought I would do a sample application on the Task Parallel Library (TPL) which was functionally equivalent to the BackgroundWorker post. As well as changing to using Tasks instead of the BackgroundWorker, I thought it might be interesting to change the GUI Framework as well from WinForms to GTK.

Known issue regarding Tasks and GTK# 2

I used MonoDevelop for writing this application which currently at the time of this post has integrated support for creating GTK# 2 applications using the Stetic GUI Designer. There is, however, a bug in GTK# 2 which prevents using Tasks to their fullest potential. The report can be viewed here on the project’s GitHub page. The necessary change for async/await made it’s way into the 2.99.1 (GTK# 3.X beta 2) release of GTK# (release notes). Unfortunately, this change is currently not planned for the GTK# 2 (2.12.X) branch. Currently, MonoDevelop only supports building GTK# 2 GUI applications with Stetic. The end result is that Glade must be used to design the GTK# 3 GUI externally from MonoDevelop.

Sample Application

This sample application computes prime numbers within the specified number range. The source code is all licensed under the Simplified BSD license (BSD 2-Clause).

Asynchronous Tasks with async/await

The first thing to note is that the async modifier makes the following method an async method. An async method itself can be awaited and most importantly it enables the await operator to be used in the scope of the method. Note that if a method marked async does not use a single await expression, then the method will actually run synchronously (don’t worry the compiler will issue a warning). The await operator places a suspension point in the code where control is given back to the caller and the thread which was previously executing is not blocked while the awaited operation continues on another thread.

In this case, we have an async event handler which spawns a potentially long operation in finding primes in a specified range. Instead of blocking the thread of the caller (GUI thread), the long operation is instead set up to run in the ThreadPool via a call to the static Run method of the Task class and the asynchronous task is awaited allowing the GUI thread to continue on and stay responsive while the operation occurs on another thread.

protected async void OnStartButtonClicked (object sender, EventArgs a)
{
	spinButton.Sensitive = false;	
	startButton.Sensitive = false;
	cancelButton.Sensitive = true;

	progressBar.Fraction = 0;
	textView.Buffer.Text = string.Empty;

	int numOfPositiveIntegersToCheck = spinButton.ValueAsInt;
	var progress = new Progress<PrimeSearchInfo> (psi => ProgressCallback (psi));
	_cts = new CancellationTokenSource ();

	try
	{
		await Task.Run (() => CheckForPrimes (numOfPositiveIntegersToCheck, _cts.Token, progress));
	}
	finally 
	{
		cancelButton.Sensitive = false;
		startButton.Sensitive = true;
		spinButton.Sensitive = true;
	}
}

Canceling and Progress Reporting with Asynchronous Tasks

Notice that along with the range value we also pass in a CancellationToken and IProgress update provider. The CancellationToken is used to check whether or not the CancellationTokenSource has signaled cancellation. Meanwhile, progress updates are reported by the provider we passed into the method.

private void CheckForPrimes(int numbersToCheck, CancellationToken cancellationToken, IProgress<PrimeSearchInfo> progress)
{
	int primeCount = 0;
	for (int i = 1; i <= numbersToCheck; i++) 
	{
		if (cancellationToken.IsCancellationRequested) 
		{
			return;	
		}

		int percentComplete = (int)Math.Round((double)(100 * i) / numbersToCheck);
		if (IsNumberPrime(i))
		{
			progress.Report (new PrimeSearchInfo (percentComplete, i));
			primeCount++;
		}
		else
		{
			progress.Report (new PrimeSearchInfo (percentComplete));
		}

		// Wait a tick to allow UI to not be flooded with update requests.
		Task.Delay (1).Wait ();
	}
}

Canceling an asynchronous operation via a CancellationToken is as simple as making a call to the Cancel() method of the corresponding CancellationTokenSource. After Cancel() is called the next time the code checks the IsCancellationRequested property of the CancellationToken it will be set to true.

protected void OnCancelButtonClicked (object sender, EventArgs a)
{
	if (_cts != null) 
	{
		_cts.Cancel ();
	}
}

Note that the progress callback interacts with GUI elements. This is possible because the Progress class captures the current SynchronizationContext when it is constructed. Whenever progress change is reported via a call to the Report method, the handler is invoked through the SynchronizationContext which was captured when the Progress object was instantiated. For example, if this Progress object was instantiated on the main thread of a WPF application it would capture the DispatcherSynchronizationContext and in this case since this is a GTK application it captures the GLibSynchronizationContext.

The problem with the GTK# 2.12.X series is that it does not implement a SynchronizationContext for GLib and therefore there is no SynchronizationContext for the Progress object to capture during construction. This can be verified by checking the static Current property of the SynchronizationContext class and seeing that it is null. The end result is that callbacks are invoked on the ThreadPool (Default) SynchronizationContext which makes updating GTK GUI elements hazardous and defeats the point of using the Progress class. Fortunately, the latest beta releases of GTK# 3 fully support async/await.

private void ProgressCallback(PrimeSearchInfo psi)
{
	progressBar.Fraction = psi.Percentage / 100.0;
	progressLabel.Text = string.Format("Progress: {0}%", psi.Percentage);
	if (psi.DiscoveredPrime.HasValue) 
	{
		textView.Buffer.Text += string.Format ("Found a Prime Number: {0}" + System.Environment.NewLine, psi.DiscoveredPrime.Value);		
	}
}

Conclusion

I hope this post helped explain how to write asynchronous code using the TPL in .Net/Mono. If you are familiar with the BackgroundWorker, then understanding TPL functionality essentially breaks down to…

BackgroundWorker TPL Equivalent
ReportProgress Method IProgress.Report Method
CancelAsync Method CancellationTokenSource.Cancel Method
RunWorkerAsync Method Task.Run Method (awaited)

The source code for the sample application can be downloaded here.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.