Bill Blogs in C# -- async

Bill Blogs in C# -- async

Created: 3/12/2011 6:38:41 PM

Today, I’ll look at one of the next set of samples Lucian created. I am skipping the UI Responsiveness section, because those don’t introduce any new techniques to discuss. If you create async operations, your UI remains responsive. Go try the site for yourself.

Let’s move on to cancellation. If you start an async task, you may to want to cancel it. You do that using the CancellationTokenSource class. The CancellationTokenSource defines and implements a handshake mechanism between different contexts when the calling context wants to cancel an operation in another context. Here’s a simple example:

        private CancellationTokenSource cts;

public async Task AsyncCancelSingle()
{
cts = new CancellationTokenSource();

try
{
WriteLinePageTitle(await DownloadStringTaskSlowNetworkAsync(
new Uri("http://www.weather.gov"), cts.Token));
}
catch (OperationCanceledException)
{
Console.WriteLine("Downloading canceled.");
}
}

There is some code that’s not part of the online sample: Lucian has added a Cancel button to the UI. It calls Cancel() on the CancellationTokenSource. In addition, the DownloadStringTaskSlowNetworkAsync() method will examine the IsCancellationRequest property of cts, and cancel, if that has been requested.

You can see that if an operation is cancelled, it reports that by throwing an OperationCancelledException.

The next sample gives an example of the logic inside the method that performs the async operation. Like before, it simulates long-running operations. Between each step of the operation, it calls cancellationToken.ThrowIfCancellationRequested(). That method does the obvious: throws the OperationCancelledException if and only if a cancel has been requested.

        private CancellationTokenSource cts;

public async Task AsyncCancelSingleCPU()
{
cts = new CancellationTokenSource();

try
{
int[] data = await ProcessAsync(GetData(), 16, 16, cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Processing canceled.");
}
}

public Task<int[]> ProcessAsync(byte[] data, int width, int height,
CancellationToken cancellationToken)
{
return TaskEx.Run(() =>
{
var result = newint[width * height];

for (int y = 0; y < height; y++)
{
cancellationToken.ThrowIfCancellationRequested();
for (int x = 0; x < width; x++)
{
Thread.Sleep(10); // simulate processing cell [x,y]
}
Console.WriteLine("Processed row {0}", y);
}

return result;
});
}

The last two samples show how to invoke Cancel support for both serial and parallel requests. In both cases, you use the same CancellationTokenSource. In the serial case (shown next), requesting cancel stops the current operation. That means any remaining operations are not started. (Control moves the catch clause.)

 

        private CancellationTokenSource cts;

public async Task AsyncCancelSingleSerial()
{
cts = new CancellationTokenSource();

try
{
WriteLinePageTitle(await DownloadStringTaskSlowNetworkAsync(
new Uri("http://www.weather.gov"), cts.Token));
WriteLinePageTitle(await DownloadStringTaskSlowNetworkAsync(
new Uri("http://www.weather.gov/climate/"), cts.Token));
WriteLinePageTitle(await DownloadStringTaskSlowNetworkAsync(
new Uri("http://www.weather.gov/rss/"), cts.Token));
}
catch (OperationCanceledException)
{
Console.WriteLine("Download canceled.");
}
}

In the parallel case (shown next), all three* operations are cancelled when the IsCancellationRequested property gets set to true.

* Actually, all of the three operations that have not completed are cancelled. If one has completed, and two are still  running, only two are cancelled. But you get the idea.

        private CancellationTokenSource cts;

public async Task AsyncCancelSingleParallel()
{
cts = new CancellationTokenSource();

try
{
Task<string> page1 = DownloadStringTaskSlowNetworkAsync(
new Uri("http://www.weather.gov"), cts.Token);
Task<string> page2 = DownloadStringTaskSlowNetworkAsync(
new Uri("http://www.weather.gov/climate/"), cts.Token);
Task<string> page3 = DownloadStringTaskSlowNetworkAsync(
new Uri("http://www.weather.gov/rss/"), cts.Token);

WriteLinePageTitle(await page1);
WriteLinePageTitle(await page2);
WriteLinePageTitle(await page3);
}
catch (OperationCanceledException)
{
Console.WriteLine("Download canceled.");
}
}

The last sample shows how to work with the cancelation token to request a cancel after a specified period of time. It’s straightforward, so look at the code and the docs.

Created: 3/11/2011 6:48:30 PM

It’s time to continue exploring the Async samples.  The next two show how to perform a series of async operations, either serially or parallel. 

This version requests three different pages and awaits each download before writing the output, then starting the next download:

        public async void AsyncIntroSerial()
{
var client = new WebClient();

WriteLinePageTitle(await client.DownloadStringTaskAsync(
new Uri("http://www.weather.gov")));
WriteLinePageTitle(await client.DownloadStringTaskAsync(
new Uri("http://www.weather.gov/climate/")));
WriteLinePageTitle(await client.DownloadStringTaskAsync(
new Uri("http://www.weather.gov/rss/")));
}

Each request finishes before the next request starts. The program performs as follows:

  1. Start the async task for weather.gov
  2. await the result of the weather.gov page, then print its title.
  3. Start the async task for the climate page
  4. await the result of the climate page, then print its title.
  5. Start the async task for the rss page.
  6. await the result of the rss page, then print its title.

Of course, the async framework can also start each async request, and only await once all requests have started:

        public async void AsyncIntroParallel()
{
Task<string> page1 = new WebClient().DownloadStringTaskAsync(
new Uri("http://www.weather.gov"));
Task<string> page2 = new WebClient().DownloadStringTaskAsync(
new Uri("http://www.weather.gov/climate/"));
Task<string> page3 = new WebClient().DownloadStringTaskAsync(
new Uri("http://www.weather.gov/rss/"));

WriteLinePageTitle(await page1);
WriteLinePageTitle(await page2);
WriteLinePageTitle(await page3);
}

page1, page2, and page3 all represent tasks (that return string). All three have started before the first await.  The three calls to WriteLinePageTitle are sequential, and each await it’s result. That means the execution goes:

  1. Start the async task for weather.gov
  2. Start the async task for the climate page
  3. Start the async task for the rss page.
  4. await the result of the weather.gov page, then print its title.
  5. await the result of the climate page, then print its title.
  6. await the result of the rss page, then print its title.

Things to Remember

await signals when you want to current evaluation to suspend until the awaited task completes. Nothing suspends until you await it.

Created: 3/9/2011 1:20:31 PM

I’ve decided to follow the process I used when I learned LINQ to the new async features coming in the next version of C#. Longtime blog readers will remember that I took each of the 101 LINQ samples, and posted my notes while running and examining them. 

Lucian Wischik has published the samples (using Silverlight) here. This is the source for what I’m writing about.

Today, I’ll cover the introductory sample. All the new language keywords are demonstrated here. The first sample shows a simple web request:

        public async void AsyncIntroSingle()
{
WriteLinePageTitle(
await new WebClient()
.DownloadStringTaskAsync(new Uri(http://www.weather.gov)));
}

This code says: create a new WebClient, and tell it to download the weather channel site asynchronously. After starting the async download, yield control to the caller of this method. When that task (the download) has finished, continue by passing its result to WriteLinePageTitle.

Let’s contrast that with the C# 4.0 version (also from Lucian’s site):

        public
        void AsyncIntroSingleBefore()
{
var client = new WebClient();

client.DownloadStringCompleted +=
AsyncIntroSingleBefore_DownloadStringCompleted;
client.DownloadStringAsync(new Uri("http://www.weather.gov"));
}

void AsyncIntroSingleBefore_DownloadStringCompleted(object sender,
DownloadStringCompletedEventArgs e)
{
WriteLinePageTitle(e.Result);
}

I hope that the first version is simpler (despite its unfamiliarity). This older version exposes more of the internal processes to make this happen.

It still creates a WebClient. But, before starting the download, this code wires up the event handler for the continuation method. Then, it starts the download. Notice that the code running after the download completes is now in another method.

Things to Remember:

Methods that have the async modifier are asynchronous methods. That means their evaluation is discontinuous. There evaluation may be suspended when an await expression is encountered.

await suspends execution of an asynchronous function until the task completes. Await may only appear in an asynchronous method (among other restrictions.) When code awaits something, the current method exits, only to have control switch back to it when the awaited operation completes.

The big concept:  These new language features enable us to write code that looks like it is executing synchronously, but actually does execute asynchronously. The code reads as a sequential program: do this, then do that, then do something else. However, it executes asynchronously: do this, (while waiting for this to continue, go keep busy), then do that, then do the other thing. The justification is that we write programs that utilize more cores, or communicate with more devices on the network. We want to write and read those programs sequentially, because they are easier to understand. These new features will make that easier.

Current Projects

I create content for .NET Core. My work appears in the .NET Core documentation site. I'm primarily responsible for the section that will help you learn C#.

All of these projects are Open Source (using the Creative Commons license for content, and the MIT license for code). If you would like to contribute, visit our GitHub Repository. Or, if you have questions, comments, or ideas for improvement, please create an issue for us.

I'm also the president of Humanitarian Toolbox. We build Open Source software that supports Humanitarian Disaster Relief efforts. We'd appreciate any help you can give to our projects. Look at our GitHub home page to see a list of our current projects. See what interests you, and dive in.

Or, if you have a group of volunteers, talk to us about hosting a codeathon event.