One of the dominant themes throughout this release of .NET is the ability to perform work in an asynchronous manner. Each release of .NET has given us new (and easier) ways to write async code such as the Asynchronous Programming Model (APM) and the Task Asynchronous Pattern (TAP) in C# 4.0. In the latest release, a new model using the new keywords async
and await
is introduced. Although this book is C# focused we will also briefly cover the new changes in VB.NET.
Let’s take a real-world example that illustrates some of the advantages and disadvantages of performing work asynchronously. A real-world frustration we all face occasionally is calling a company that leaves us on hold for a very long time—tax departments and telecom organizations are, of course, notorious for doing this. Let’s imagine we have a query about our taxes and need to call up the taxation department. We call up the department and we are automatically put on hold. You could think of this situation as being similar to a program operating synchronously so that until our call is answered, we are prevented from doing anything else.
Of course, in this scenario what most of us tend to do is then put the call on speaker phone and do something else, like find cat videos on YouTube. This could be compared to implementing multithreading in an application as it involves a context switch to see if the call has been answered yet.
Let’s imagine, however, that instead of making you wait, these companies have a new system that will automatically call you back if they are busy at the time of your call (some organizations already do this). With this facility, you are free to go and do other tasks without having to monitor the phone call with the knowledge that a representative from the company will call you as soon as one is available. This could be seen as similar to performing a task asynchronously.
There are a few main reasons why you would want to run code asynchronously in your applications:
- To keep your application responsive while waiting for something that is likely to take a long time to complete
- To make your application as scalable as possible (particularly important for services and ASP.NET that have limited threads/resources to service requests)
- To ensure growth in cloud applications/storage and latency, which makes performing actions asynchronously more important
However, not all problems are suited to be run asynchronously through. As a rough guide, consider using async functionality in the following situations:
- You are waiting for a response from a remote service.
- The task you want to perform is computationally expensive and doesn’t complete very quickly (otherwise the overhead may exceed any benefits).
.NET already contains functionality to run code in an asynchronous manner, but the 4.5 features have the following advantages:
- Less complicated code—it’s all wrapped up in one method
- Much tidier because the callee method is responsible for handling its work rather than the method calling it
- Easier exception handling
- More defined methods (via interfaces) of handling cancellation and reporting on progress
Handling complications regarding thread synchronization contexts. OK, enough theory—let’s take a look at how this is done.
C# 5.0 introduces two new keywords—async
and await
—for running a method asynchronously. Let’s look at a simple example of using these now. Open up Visual Studio 2012, create a new Console application, and enter the following code:
public class Program
{
static void Main(string[] args)
{
DoWorkAsync();
//I get called immediately & before PretendToDoSomething is completed
Console.WriteLine("Main method is all done");
Console.ReadKey();
}
static async void DoWorkAsync()
{
await Task.Run(() => PretendToDoSomethingAsync());
Console.WriteLine("Once PretendToDoSomething is run I will be printed");
}
static void PretendToDoSomethingAsync()
{
System.Threading.Thread.Sleep(2000);
Console.WriteLine("I have finished pretending to do something");
}
}
The screenshot in Figure 5-1 shows the output of running this program.
Let’s summarize what happened:
- The
DoWorkAsync
method was called.DoWorkAsync
created a new task calledPretendToDoSomethingAsync
. As this is marked with theawait
keyword, control flow returned immediately to the main thread until this method was completed.- “Main method is all done” was output.
- Once the
Thread.Sleep
was completed, thenPretendToDoSomethingAsyn
c output “I have finished pretending to do something”.- Control then resumed to
DoWork
method that could print out “OncePretendToDoSomething
is run I will also be printed”.
We could actually do something very similar in C# 4.0 as follows:
static void Main(string[] args)
{
Task.Run(() =>
{
System.Threading.Thread.Sleep(2000);
Console.WriteLine("I have finished pretending to do something");
}).ContinueWith((result) =>
{
Console.WriteLine("Once PretendToDoSomething is run I will be printed");
});
//I get called immediatly & before PretendToDoSomething is completed
Console.WriteLine("Main method is all done");
Console.ReadKey();
}
So, what is the await
keyword doing? The await
keyword stops the async
method that was called until the awaited method is completed. In the meantime, the control flow goes back to the code that first called the method.
This example isn’t too useful and could be easily accomplished with current functionality, but let’s say we wanted to perform several items of work within a loop (for example, calling several services); the new keywords make this trivial to do:
static async void DoWorkAsync()
{
for (int i = 0; i < 5; i++)
{
await Task.Run(() => PretendToDoSomethingAsync());
Console.WriteLine("End of iteration " + i);
}
}
There are a couple of rules and conventions you should be aware of when using the Async functionality:
- The compiler will give you a warning for methods that contain
async
modifier but noawait
operator.- A method that isn’t using the
await
operator will be run synchronously.- Microsoft suggests a convention of post-fixing an Async method with the words
Async
e.g.DoSomethingAsync
, which seems sensible.
Async methods can return any of the following:
void
(note an Async method that returnsvoid
cannot itself be awaited)Task
Task<T>
A number of BCL methods now have an Async version for you to use with the await
keyword (see Chapter 3 for more information). The following example demonstrates the WebClient
downloading the content of a web page using one of these new async methods:
class Program
{
static void Main(string[] args)
{
DoWork();
}
static async void DoWork()
{
string url = “http://www.microsoft.com”;
string content = await new WebClient().DownloadStringTaskAsync(url);
}
}
One of the language team’s goals when developing the async functionality was to make it easy to integrate into existing code. One of the aspects of this is, of course, exception handling. In the next piece of code, we have two synchronous methods: PrintMessage
, which in turn calls ProcessMessage
. ProcessMessage
will throw an exception if the value passed to it is null or empty, so in PrintMessage
the call is wrapped in a try/catch
block.
class Program
{
static void Main(string[] args)
{
PrintMessage("");
Console.WriteLine("Waiting...");
Console.ReadKey();
}
private static void PrintMessage(string name)
{
try
{
var result = ProcessMessage(name);
Console.WriteLine(result);
}
catch (ArgumentNullException ane)
{
Console.WriteLine(ane.Message);
}
}
private static string ProcessMessage(string name)
{
Thread.Sleep(1000);
if (string.IsNullOrEmpty(name))
{
throw new ArgumentNullException("name", "name must have a valid value.");
}
return string.Format("Hi there {0}", name);
}
}
If you run this code, the exception, as would be expected, gets caught and handled.
Now let’s change these two methods to be asynchronous (if you are trying this out, don’t forget to change the method being called in Main
to PrintMessageAsync
).
private static async Task PrintMessageAsync(string name)
{
try
{
var result = await ProcessMessageAsync(name);
Console.WriteLine(result);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private static async Task<string> ProcessMessageAsync(string name)
{
await Task.Delay(1000);
if (string.IsNullOrEmpty(name))
{
throw new ArgumentNullException("name", "name must have a valid value.");
}
return string.Format("Hi there {0}", name);
}
As you can see, with the exception of adding the async
and await
keywords, we haven’t had to change anything in the PrintMessage
method. If you run the code, the exception is caught and handled exactly as it was in the synchronous version.
Under the covers, what is actually happening to facilitate changing methods from synchronous to asynchronous is that the first exception in the Task
’s AggregateException
is thrown. In most cases, this behavior is acceptable, but there are instances where another approach will be necessary.
Take, for example, the following code where in the DoSomething
method we await another two methods, both of which will throw exceptions (if you want to try this, just create a WPF project and add a button to the form):
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
try
{
var result = await DoSomething();
MessageBox.Show(result);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private async Task<string> DoSomething()
{
var task1 = Action1Async(true);
var task2 = Action2Async(true);
var result = await Task<string[]>.WhenAll(task1, task2);
return string.Join("
", result);
}
private async Task<string> Action1Async(bool throwException = false)
{
await Task.Delay(2000);
if (throwException)
throw new IOException();
return "action 1 completed";
}
private async Task<string> Action2Async(bool throwException = false)
{
await Task.Delay(2000);
if (throwException)
throw new ArgumentNullException();
return "Action 2 completed";
}
Even though both Action1Async
and Action2Async
threw exceptions, the only one that gets reported is the IOException
thrown by the first method. As you can imagine, this could result in some interesting side effects in your code and become problematic when debugging. One solution is to modify the DoSomething
method to use ContinueWith
and an instance of TaskCompletionSource
to pass back the AggregationException
to the calling method.
private Task<string> DoSomething()
{
var task1 = Action1Async(true);
var task2 = Action2Async(true);
var tcs = new TaskCompletionSource<string>();
Task<string[]>.WhenAll(task1, task2)
.ContinueWith(tsk =>
{
if (tsk.IsFaulted)
{
tcs.SetException(tsk.Exception);
}
else
{
tcs.SetResult(string.Join("
", tsk.Result));
}
});
return tcs.Task;
}
Something you need to consider when writing async methods, especially long-running ones, is providing a way to cancel and bail out of the process. This is easily done by modifying or providing an overload of your method to take a CancellationToken
as a parameter. Within your method, you would then query the token’s IsCancellationRequested
property to see if a cancellation has been requested and if it has do any clean up that is necessary and either exit the method or call the token’s ThrowIfCancellationRequested
method.
The following trivial example illustrates a possible implementation:
class UsingCancellationTokens
{
public async static void Run()
{
var cts = new CancellationTokenSource(3000);
await LongRunningProcess(1000, cts.Token).ContinueWith(tsk =>
{
if (tsk.IsCanceled)
Console.WriteLine("Process cancelled");
else
Console.WriteLine("Process completed");
});
}
private async static Task LongRunningProcess(int counter, CancellationToken token)
{
for (int i = 0; i < counter; i++)
{
await Task.Delay(100);
if (token.IsCancellationRequested)
{
token.ThrowIfCancellationRequested();
}
Console.WriteLine(i);
}
}
}
Given the relative simplicity of implementing asynchronous methods using the async
/await
keywords, clearly there is more going on than meets the eye. Before we look at what is happening under the covers, let’s first review what is actually happening when we use the async
and await
keywords.
When you call an async method, it runs until it encounters an awaitable method (that is, a method marked with await
). At that point, it returns to the calling code and then happily continues running. Once the awaited method completes, however, any other code in the async method is then processed.
But how is the compiler achieving this magic?
Behind the scenes, the compiler is generating code for managing the state of your method and intercepting calls to it. When a method is tagged with the async
keyword, the compiler creates a helper state machine for the method. A stub method is created with a signature corresponding to the original method but with a shiny new implementation containing code for setting up the state machine and initiating it with a call to its MoveNext
method.
The state machine then maintains what’s going on across asynchronous await points. Within it, if required, it will contain the method’s original code but segmented to allow results and exceptions to pass into the returned Task
and facilitate continuation after an await
.
Phew, that is a lot to take in!
Note Stephen Toub, in his MSDN article on Async performance (http://msdn.microsoft.com/en-us/magazine/hh456402.aspx
), said that when you start working with asynchronous methods, “a new mental model is needed.”
At this point, we would crack open our preferred IL decompiler and look at how the compiler rewrites an async method. Instead, taking inspiration from Mads Torgersen’s article “Pause and Play with Await” (http://msdn.microsoft.com/en-us/magazine/hh456403.aspx
), we are going to hand roll our own state machine to mimic async/await functionality and understand what’s really going on here.
First, here is the method we are going to make asynchronous:
public void GetHtml(string url)
{
var webClient = new WebClient();
try
{
var result = webClient.DownloadString(url);
Console.WriteLine(result);
}
catch (WebException webEx)
{
Console.WriteLine(webEx.Message);
}
finally
{
webClient.Dispose();
}
}
- To get started, create a new console application project.
- Once you have that up, add a new class and call it
Example
, open it, and addusing
statements for the following namespaces:
System.Diagnostics
System.Net
System.Runtime.CompilerServices
System.Threading
System.Threading.Tasks
- Within the
Example
class, you are going to add a nested type to act as our state machine. The following code defines your state machine:private struct GetHtmlAsyncStateMachine
{
public int state;
public AsyncTaskMethodBuilder builder;
public string result;
private WebClient webClient;
public string url;
private TaskAwaiter<string> awaiter;
public void MoveNext()
{
try
{
if (state == 1)
goto doStuff;
webClient = new WebClient();
doStuff:
try
{
if (state == 1)
goto completed;
awaiter = webClient.DownloadStringTaskAsync(url).GetAwaiter();
if (!awaiter.IsCompleted)
{
state = 1;
//Specify this method as the continuation action
//for when the await action completes
awaiter.OnCompleted(this.MoveNext);
return;
}
completed:
result = awaiter.GetResult();
//continue processing at this point
Console.WriteLine(result);
}
catch (WebException webEx)
{
Console.WriteLine(webEx.Message);
}
}
catch (Exception ex)
{
state = 2;
builder.SetException(ex);
return;
}
finally
{
webClient.Dispose();
}state = 2;
builder.SetResult();
}
}- Next, you will modify your original method to function in an async manner by instantiating the state machine and then kick it off by calling its
MoveNext
method. Add the next piece of code to theExample
class:public Task GetHtmlAsync(string url)
{
var stateMachine = new GetHtmlAsyncStateMachine();
stateMachine.builder = AsyncTaskMethodBuilder.Create();
stateMachine.url = url;
stateMachine.MoveNext();
return stateMachine.builder.Task;
}- Finally, to see this all working, open
Program.cs
and in theMain
method add the following:var example = new Example();
example.GetHtmlAsync("http://www.microsoft.com");
Console.WriteLine("Waiting ...");
Console.ReadKey();
With this example what we have done, in a simplified form, is essentially what the compiler does when you use the async
/await
keywords on a method. So, for example, if we take our original synchronous and modify it to be asynchronous like so:
public async Task GetHtmlAsync(string url)
{
var webClient = new WebClient();
try
{
var result = await webClient.DownloadStringTaskAsync(url);
Console.WriteLine(result);
}
catch (WebException webEx)
{
Console.WriteLine(webEx.Message);
}
finally
{
webClient.Dispose();
}
}
Here is how it would look when viewed in an IL decompiler:
public class Example
{
[DebuggerStepThrough, AsyncStateMachine(typeof(<GetHtmlAsync>d__0))]
public Task GetHtmlAsync(string url)
{
<GetHtmlAsync>d__0 d__;
d__.<>4__this = this;
d__.url = url;
d__.<>t__builder = AsyncTaskMethodBuilder.Create();
d__.<>1__state = -1;
d__.<>t__builder.Start<<GetHtmlAsync>d__0>(ref d__);
return d__.<>t__builder.Task;
}
// Nested Types
[CompilerGenerated]
private struct <GetHtmlAsync>d__0 : IAsyncStateMachine
{
// Fields
public int <>1__state;
public Example <>4__this;
public AsyncTaskMethodBuilder <>t__builder;
private object <>t__stack;
private TaskAwaiter<string> <>u__$awaiter3;
public string <result>5__2;
public WebClient <webClient>5__1;
public string url;
// Methods
private void MoveNext()
{
try
{
bool <>t__doFinallyBodies = true;
switch (this.<>1__state)
{
case -3:
goto Label_0121;
case 0:
break;
default:
this.<webClient>5__1 = new WebClient();
break;
}
try
{
int CS$4$0000 = this.<>1__state;
if (CS$4$0000 == 0)
{
}
try
{
TaskAwaiter<string> CS$0$0001;
CS$4$0000 = this.<>1__state;
if (CS$4$0000 != 0)
{
CS$0$0001 = this.<webClient>5__1.DownloadStringTaskAsync(this.url).GetAwaiter();
if (!CS$0$0001.IsCompleted)
{
this.<>1__state = 0;
this.<>u__$awaiter3 = CS$0$0001;
this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>,
Example.<GetHtmlAsync>d__0>(ref CS$0$0001, ref this);
<>t__doFinallyBodies = false;
return;
}
}
else
{
CS$0$0001 = this.<>u__$awaiter3;
this.<>u__$awaiter3 = new TaskAwaiter<string>();
this.<>1__state = -1;
}
string result = CS$0$0001.GetResult();
CS$0$0001 = new TaskAwaiter<string>();
string CS$0$0003 = result;
this.<result>5__2 = CS$0$0003;
Console.WriteLine(this.<result>5__2);
}
catch (WebException webEx)
{
Console.WriteLine(webEx.Message);
}
}
finally
{
if (<>t__doFinallyBodies)
{
this.<webClient>5__1.Dispose();
}
}
}
catch (Exception <>t__ex)
{
this.<>1__state = -2;
this.<>t__builder.SetException(<>t__ex);
return;
}
Label_0121:
this.<>1__state = -2;
this.<>t__builder.SetResult();
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine param0)
{
this.<>t__builder.SetStateMachine(param0);
}
}
}
True, there is a lot more going on here, but in essence it is the same as our hand-rolled version. However, instead of us having to write it, the IL compiler is doing all the heavy lifting for us.
The ease with which the async
/await
keywords allows us to create asynchronous methods is beguiling, but as you can see from the previous example, there is more to it than meets the eye. This, in turn, can lead to performance issues. Stephen Toub covers these issues in depth in his article mentioned earlier, but here some key points to consider:
- Methods should be chunky, not chatty. When working synchronously, using small, discreet methods incurs little cost when compiled. It is a different story when we switch to an asynchronous approach. For any given async method, the compiler has to, as we have seen, generate a fair amount of extra scaffolding to make a method asynchronous and that is an extra cost. So, where possible, it is worthwhile consolidating a logical piece of functionality into a single method.
- Beware that any variables within an async method will be lifted into the state machine and this can impact on garbage collection.
- Consolidate multiple awaits. If you have multiple awaits, consider rationalizing them into a single await by using the
Task.WhenAll
method.- Know when not to async.
Another new feature that has been added to both C# and VB.NET is caller information attributes. Defined in the System.Runtime.CompilerServices
namespace, there are three attributes:
CallerFilePathAttribute
, which returns the full path to the source file of the caller as of compile timeCallerLineNumberAttribute
, which identifies the line number in the source file where the method was calledCallerMemberNameAttribute
, which holds the name of the method or property of the caller
To use these attributes, you need to apply them to optional parameters on a method with a default value. For example:
SomeMethod(string message, [CallerFilePath] string sourceFile = "",
[CallerLineNumber] int sourceLineNo = 0, [CallerMemberName] string memberName = "")
The attributes don’t make the parameters optional but determine the default value when the argument is omitted. Unlike the StackTrace
property of an exception, they are not affected by obfuscation since they are injected as literals into the IL.
Essentially, the idea behind these attributes is to facilitate debugging, tracing, and the creation of diagnostic tools. The following example shows how this could be used:
class Program
{
static void Main(string[] args)
{
Trace.Listeners.Clear();
Trace.Listeners.Add(new ConsoleTraceListener());
var repository = new ProjectRepository();
repository.AddProject(new ProjectModel { ProjectName = "Test Project" });
Console.ReadKey();
}
}
public class ProjectModel
{
public string ProjectName { get; set; }
}
public class ProjectRepository
{
public void AddProject(ProjectModel project,
[CallerFilePath]string srcFilePath = "",
[CallerLineNumber] int srcLineNo = 0,
[CallerMemberName] string memberName = "")
{
Trace.WriteLine("Project Added");
Trace.WriteLine("Member name: " + memberName);
Trace.WriteLine("Source file path: " + srcFilePath);
Trace.WriteLine("source line number: " + srcLineNo);
}
}
Running this example would generate something similar to Figure 5-2.
Though useful for diagnostic purposes, using these attributes adds clutter to your method signatures, which you do need to consider if you are going to use them. Outside of the diagnostics realm, the CallerMemberNameAttribute
comes into its own when implementing the INotifyPropertyChanged
interface.
When implementing this interface on, say, a view model, a common practice is to create a method that takes a property name as a string literal to then raise the PropertyChanged
event—for example, OnPropertyChanged("PropertyName")
. This can introduce subtle bugs if the property name is changed, but the value in the method call isn’t. An alternative to this was to create a method that took an expression, OnPropertyChanged<T>(Expression<Func<T>> e)
, that could then be called as follows: OnPropertyChanged(() => this.PropertyName)
. By using the CallerMemberNameAttribute
the whole thing can be simplified as seen in this code example:
public class ProjectVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ProjectModel model;
public string ProjectName
{
get { return model.ProjectName;}
set
{
model.ProjectName = value;
OnPropertyChanged();
}
}
private void onPropertyChanged([CallerMemberName] string memberName = "")
{
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs(memberName));
}
}
Although we are mainly focusing on C# in this book, we want to make you aware that VB.NET has been enhanced with a number of features to bring it into line with C#.
Call Hierarchy was introduced to C# in Visual Studio 2010 and allows you to see where various methods are being called from and what they call from the IDE. This feature is now available in VB.NET as well. (See Figure 5-3.)
An iterator is a type of collection object that allows a client to iterate through each object in the collection. Microsoft defines it as the following:
An iterator is a method, get accessor or operator that enables you to support foreach iteration in a class or struct without having to implement the entire IEnumerable interface. Instead, you provide just an iterator, which simply traverses the data structures in your class. When the compiler detects your iterator, it will automatically generate the Current, MoveNext and Dispose methods of the IEnumerable or IEnumerable<T> interface.
http://msdn.microsoft.com/en-us/library/dscyy5s0(v=vs.80).aspx
Iterators are particularly useful for wrapping up complex logic to transverse complex structures, for example, a hierarchical tree. When you use an iterator behind the scenes, the compiler generates a state machine (for more information, see www.abhisheksur.com/2011/01/internals-to-c-iterators.html
) that tracks the current item and provides get next methods each time it is called.
Iterators were first introduced in the first release of C# and have now been introduced into VB.NET. The following example shows how to create an iterator that will return a name from a predefined list each time it is called:
Sub Main()
For Each name As String In NamesIterator()
Console.WriteLine(name)
Next
Console.ReadKey()
End Sub
Private Iterator Function NamesIterator() As System.Collections.Generic.IEnumerable(Of String)
Dim ListOfNames As New List(Of String)
ListOfNames.Add(“Karen”)
ListOfNames.Add(“Trish”)
ListOfNames.Add(“Billy”)
For Each name In ListOfNames
Yield name
Next
By constructing your namespaces in a specific manor, it is possible to block access to certain namespaces. For example, the following will not compile (when the error Type ‘System.Guid’ is not defined) as the use of MyNamespace.System
prevents access to .NET’s System
namespace:
Namespace MyNamespace
Namespace System
Public Class SomeClass
Function DoSomething()
Dim myGuid = New System.Guid
End Function
End Class
End Namespace
End Namespace
If you find yourself in this situation, you probably want to refactor your namespaces. However, if you can’t for some reason or you are using third-party libraries, you can use the Global
keyword to tell .NET to start at the outermost namespace (for more on Global
see the next section):
Dim myGuid = New Global.System.Guid
In Visual Basic, all namespaces are based on the project’s root namespace (often the project’s name, for example, ConsoleApplication1
—you can override this in project properties). The Global
keyword allows you define a namespace outside of this:
Namespace Global.OutsideTheProjectNamespace
End Namespace
Or the more verbose:
Namespace Global
Namespace OutsideTheProjectNamespace
End Namespace
End Namespace
The Global
keyword also allows you to specify namespaces that would clash with .NET’S namespace—note that this will only work if you prefix them:
Namespace MyNamespace
Namespace System
Public Class SomeClass
Sub DoSomething()
Dim obj As New Global.System.IO.AlexClass
Dim wouldntWorkWithoutGlobal As Global.System.IO.StreamReader
End Sub
End Class
End Namespace
End Namespace
Namespace Global.System.IO
Class AlexClass
End Class
End Namespace
This seems like a bad idea to me and would likely cause you more hassle than it’s worth, so I would avoid it.
Jake Ginnivan, MVP VSTO http://jake.ginnivan.net/
In January 2012, we decided that we would upgrade a large WPF application (from a two-year-old project) to use the async CTP. We put a lot of effort into making sure that the application never blocked the UI thread and gave the user visual feedback when the application was busy. The application also had a suite of UI automation tests that understood when the application is doing background work so the test doesn’t continue.
This meant that our code suffered from a fair amount of lambda tax. As an example, a WCF call looked something like this:
_uiService.CallWithBusy(
_someService,
service => service.ServiceCall(arg1, arg2),
complete =>
{
if (DoDefaultErrorHandling(complete))
return;
// Update UI etc
}
So, our main motivation for adopting the async CTP was to make our code cleaner and easier to write. Now a service call looks like the following:
Try
{
var result = await _someServiceAsync.ServiceCall(arg1, arg2);
// Update UI etc
}
Catch (Exception ex)
{
ex.DoDefaultErrorHandling(_uiService);
}
These examples are simple, but often we had multiple service calls or other async tasks. It is a very common occurrence in rich clients that you need to use the result of one call to pass to another. This makes it very hard to follow the logic and flow of the code. There were places in the code base where we reduced 100 lines of code to about 30 just by using the async
/await
keywords. After using these language features, it makes it very hard to go back!
For us, async
/await
is like LINQ—we really struggle to go back and do it the old way. We are also now adopting ASP.NET Web API and making use of the support for Asynchronous Controller actions, allowing us to get much greater throughput on our central server. This is what makes the async stuff so great—it can help you write fast asynchronous code, but even if you don’t need the additional throughput, the async language features in .NET 4.5 can help you write cleaner code as well.
The async
and await
keywords will make it much easier and intuitive to integrate Asynchronous functionality into your applications. It is probably a sign of the maturity of both C# and VB.NET that the only other new feature in this release has been the introduction of Caller Info attributes.