Async 12: Expressions within await

Update:  Thanks to Jon Skeet, the description of what can be awaited is more accurate.

After a brief spring hiatus, it’s time to continue examining the Async samples provided by Lucian Wishik.

The next set of samples shows how the await keyword integrates with other language features and expression types. This set, which has several samples, shows how well the new language features integrate with existing language features.

The key takeaway:  The await keyword can await any expression that has an appropriate GetAwaiter() method. This includes any expression that returns a Task, or a Task<T>.

Corollary: Any expression that returns Task or Task<T> may be awaited.

For example, the first sample in this set shows that you may await a member access expression:

        public async void AsyncMemberAccessWithin()
{
var pair = new StringTaskPair();
pair.Task1 = new WebClient().DownloadStringTaskAsync(
new Uri("http://www.weather.gov"));
pair.Task2 = new WebClient().DownloadStringTaskAsync(
new Uri("http://www.weather.gov/climate/"));

WriteLinePageTitle(await pair.Task2);
}

publicclass StringTaskPair
{
public Task<string> Task1 { get; set; }
public Task<string> Task2 { get; set; }
}

Note that StringTaskPair.Task1 and StringTaskPair.Task2 are both typed as Task<string>. You can set them to expressions that return Task<string>. Because they are Task<string>, you can await the results of those actions.

The remaining samples show many different expressions in the C# language. You can await any of those expressions.  There are a few that are non-obvious that deserve special mention.

You can await the result of invoking a delegate:

        public async void AsyncInvocationWithin()
{
var topic = "monkeys";

Func<string, Task<string>> searchTaskGenerator =
s => new WebClient().DownloadStringTaskAsync(
new Uri(string.Format(
"http://odata.netflix.com/Catalog/Titles/$count?$filter=substringof('{0}',Name)", s)));

Console.WriteLine("{0} movies about {1}.", await searchTaskGenerator(topic), topic);
}

You can use conversion operators to coerce a result to a Task<T> that can be awaited:

        public async void AsyncExplicitConversionWithin()
{
var sr = new StringCalculation();

Console.WriteLine(await (Task<string>)sr);
}

publicclass StringCalculation
{
publicstaticexplicitoperator Task<string>(StringCalculation sr)
{
return TaskEx.Run(() =>
{
Thread.Sleep(1000); // Simulate calculation to produce a string.
return"FooBar";
});
}
}

The sample above demonstrates that you can create overloaded operators that leverage the async pattern. You can see the overloaded explicit conversion operator. In fact, you can create overloaded operators that change the standard signature such that the result of the operator is async.

For example, many of the samples in this section leverage relational operators that leverage the async pattern. This samples shows how you can define an async operator == that will tell you *some time in the future* whether or not two remote integers are the same:

        public async void AsyncEqualsWithin()
{
RemoteInteger remoteInt1 = await RemoteInteger.CreateAsync(200);
RemoteInteger remoteInt2 = await RemoteInteger.CreateAsync(34);

Console.WriteLine(await (remoteInt1 == remoteInt2));
}

publicclass RemoteInteger
{
privateintvalue; // Hold the value that we're simulating remote storage of.

publicstatic Task<RemoteInteger> CreateAsync(int i)
{
// Simulate sending i to a remote server:
var creation = new Task<RemoteInteger>(() => new RemoteInteger(i));
creation.Start();
return creation;
}
private RemoteInteger(int i) { value = i; }

publicstatic Task<bool> operator ==(RemoteInteger i1, RemoteInteger i2)
{
// Simulate remote equality comparison of i1 and i2:
var equality = new Task<bool>(() => (i1.value == i2.value));
equality.Start();
return equality;
}
publicoverridebool Equals(object obj) { thrownew NotSupportedException(); }
publicoverrideint GetHashCode() { thrownew NotSupportedException(); }
}

It’s important to note that this version of operator==() returns Task<bool> instead of bool.  By doing so, it can be awaited, as you see in the AsyncEqualsWithin() method.

Disclaimer: This is really new syntax, and I’ve had minimal experience with it.

OK, now that the disclaimer is out of the way, here’s my opinion: I like the fact that the languages team (C# and VB.NET) enables async semantics on all these expression types. However, this should be used with caution. Developers will have the natural expectation that these operators are synchronous and complete quickly. This is no different than the expectation that property accessors are quick operations. Even though it is syntactically correct, async overloaded operators will have characteristics that developers find surprising. My initial reaction is that this is not a good way to leverage these features.

Overall, it’s fantastic to see how well integrated the async keyword is with the rest of the C# language. It makes it easier to exted designs to include async capabilities with minimal extra ceremony. As we all gain more experience using these features, we’ll develop guidance for creating idioms that work well and set correct expectations for other developers.

Created: 5/6/2011 2:05:50 PM

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.