In the previous post we saw some use cases of the Task object and the async / await keywords. We saw how we can run two long tasks, one dependent on the other, without blocking the main thread and the user interface. In our example we used an async event handler, which is marked with a void return type.
Now I wan’t to show you how you can create an awaitable method or an asynchronous method which returns a Task object that can be awaited.
Let’s build a new test application…
As an example, I want to build something useful. I’m going to build a Windows application that lists all files larger than a certain size (here you can download the full source code). We are going to create an asynchronous method who will take care of the files in the file system, searching for files larger than a certain threshold. Moreover, I will also show you how you can cancel a running task using a CancellationToken object.
Here you can see the main window of the program:
When you click the “Start” button the following event handler is called:
... there is some more code here...
_Cts = new CancellationTokenSource();
BtnStart.Enabled = false;
BtnStop.Enabled = true;
try
{
List<MyFileInfo> Result = await ScanDirectoryAsync(TxtPath.Text, MaxFileSize, _Cts);
if (Result.Count == 0)
MessageBox.Show("Search completed, but no files were found.", "TDP Big File Finder", MessageBoxButtons.OK, MessageBoxIcon.Information);
else
{
MessageBox.Show("Search completed.", "TDP Big File Finder", MessageBoxButtons.OK, MessageBoxIcon.Information);
DgResults.DataSource = Result.OrderByDescending(r => r.Size).ToList();
}
BtnStart.Enabled = true;
BtnStop.Enabled = false;
}
catch (OperationCanceledException)
{
BtnStart.Enabled = true;
BtnStop.Enabled = false;
LblStatus.Text = "Operation stopped by the user.";
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString(), "TDP Big File Finder",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
I will give you some explanation… to manage the cancellation of the asynchronous task you have to use a CancellationTokenSource object. An instance of this class is stored in the form variable named “_Cts”, because we will need to use it when the button “Stop” is clicked.
The “ScanDirectoryAsync” is the method that recursively executes the search. It is an asynchronous method, because it returns a Task object. I need to use the generic version of Task, which allows you to specify the type of the return value. Sometimes you may not have a return value: in such cases you can use the non generic version of Task, which is as your function returns void. As you may have already noted, the method is called with the “await” keyword, meaning that we are waiting for the result, but the application is not blocked.
This is the code for the “ScanDirectoryAsync” method:
private Task<List<MyFileInfo>> ScanDirectoryAsync(string directoryName, long maxSizeBytes, CancellationTokenSource cts)
{
Task<List<MyFileInfo>> T = new Task<List<MyFileInfo>>(() =>
{
List<MyFileInfo> Result = ScanDirectory(directoryName, maxSizeBytes, cts);
return Result;
}, cts.Token);
T.Start();
return T;
}
The method above only creates and returns a new task object, which internally calls the method “ScanDirectory” which executes the job.
Note that the CancellationTokenSource object contains a reference to a CancellationToken object, which can be referred using the Token property. The CancellationToken object must be passed when you instantiate the Task object.
How to use the cancellation token
Now, how can you cancel the job when the user clicks the “Stop” button?
This is the click event handler for the “Stop” button:
private void BtnStop_Click(object sender, EventArgs e)
{
if (_Cts != null)
{
_Cts.Cancel();
_Cts = null;
}
}
As you can see, after checking if we have a valid object in the _Cts member, which should have been instantiated in the click event handler of the “Start” button, the method “Cancel” is called. Now there are a few options to intercept the cancellation. The most simple is to poll the value of the property IsCancellationRequested of the CancellationTokenSource object and, if true, throw an OperationCanceledException exception.
This is exactly what happens in the ScanDirectory method:
private List<MyFileInfo> ScanDirectory(string directoryName, long maxSizeBytes, CancellationTokenSource cts)
{
... some more code...
foreach (string FileName in Files)
{
... some more code...
if (cts.IsCancellationRequested)
throw new OperationCanceledException(cts.Token);
... some more code...
}
... some more code...
}
If you look at the code for the click event handler of the “Start” button, you will see that the OperationCanceledException is catched there. Just a last note: since Visual Studio is configured to stop at every exception when in debug mode, when the debugger breaks on the line that thrown the OperationCanceledException press F5 to resume the execution.
Another option to see the “Stop” button in action without interruption is run the application without debug using Ctrl + F5.
That’s all for now, but remember that I’m going to publish a new post (the last one) on this topic. See you soon!