May 31st, 2012

What’s New for Parallelism in Visual Studio 2012 RC

Stephen Toub - MSFT
Partner Software Engineer

In September, I blogged about what was new for parallelism and asynchrony in the Visual Studio 2012 Developer Preview, and in February I followed that up with a post on what was new in the Beta.  Now that Visual Studio 2012 Release Candidate is out, I want to share a few thoughts on what’s new in the Release Candidate.

Most new features for a release of Visual Studio and the .NET Framework show up prior to a Release Candidate, which is typically focused on polish, performance, and the like.  That’s true for our work around asynchrony and parallelism in Visual Studio 2012, with a fair amount of work being done under the covers to improve stability and performance.  However, there are still some visible features you’ll see show up for the first time in the Release Candidate.

Compilation Changes

Between the Developer Preview and the Beta, there were some significant changes made to how the C# and Visual Basic compilers compiled async methods.  We pushed to get as many such necessary changes made by the Beta so as to minimize that kind of churn for the Release Candidate and beyond.  As such, the patterns employed by the compiler remain the same since Beta.

However, there is one new addition that will be of most value to tool developers.  If you use a tool like ildasm.exe to look at a compiled async method, you’ll notice a new attribute is now used.  For example, the method:

public static async Task SomeMethodAsync() { … }

will end up getting compiled something like the following:

[AsyncStateMachine(typeof(<SomeMethodAsync>d__d))]
[DebuggerStepThrough]
public static Task SomeMethodAsync() { … }

The employed AsyncStateMachineAttribute contains a reference to the state machine type the compiler generated for this async method.  This allows a tool to easily navigate from an async method stub method to the state machine type that backs it.

Performance

As was true from the Async CTP to the Developer Preview and from the Developer Preview to the Beta, for the Release Candidate a lot of work has gone into tuning the performance of the new async story.  Here are a few examples of ways in which we’ve tweaked the system. (As is typically true of performance improvements, this is all implementation detail and could of course change in the future.)

At the runtime level, we’ve focused a lot on the performance of the Framework components that back the code generated by the compiler when you use the async/await keywords.  For the Release Candidate, we’ve made multiple changes here that further reduce the number and size of allocations used by the system, bringing the overhead down to a bare minimum.  We’ve also looked at allocations in systems closely related to Tasks and asynchrony, such as CancellationToken; as an example, we reduced the typical memory usage of CancellationTokenSources created by CreateLinkedTokenSource by around 40%. Such memory improvements also help with improved throughput, but in addition we focused specifically on improving throughput by trimming out as much code as possible from key async-related fast paths throughout the Task Parallel Library.  For example, we improved the lifecycle performance of creating and completing a TaskCompletionSource<TResult> by around 25%.

Additional async-related performance improvements were made throughout the base class libraries (BCL).  For example, StreamReader.ReadLineAsync has seen significant performance improvements, in some cases improving throughput by as much as 300%. BufferedStream’s implementations of ReadAsync and WriteAsync were further optimized for the common case of reading already-buffered data from the stream or writing to the stream an amount of data that would fit entirely in the buffer; both of these cases result in the operations completing synchronously, and those cases were optimized to achieve significant performance boosts.  Related, the AsStream, AsStreamForRead, and AsStreamForWrite extension methods in System.Runtime.WindowsRuntime.dll now use BufferedStream internally, yielding performance gains due to avoiding repeated interop costs.  And so on.

ASP.NET

ASP.NET has made several useful improvements around asynchrony for the Release Candidate.

One important addition has to do with async methods on an ASP.NET Page.  It was natural in the Beta to expect that you could just add the async keyword to a page-level method, e.g.

async void Button1_Click(object sender, EventArgs e)
{
    … // code that uses await
}

However, code that did this was potentially quite buggy.  The issue here is that async void methods don’t give back a handle that ASP.NET can use to monitor the operation for completion, and thus it relies on its SynchronizationContext (and notifications from an async void method to that SynchronizationContext) to know when all of the async work is completed.  In Beta, ASP.NET only had a single synchronization point which checked and asynchronously waited for all async operations to complete, and this synchronization point occurred fairly late in the page lifecycle.  Work like data binding and generating the control tree occur before that synchronization point.  Thus, if the async void method tried to do anything whose output would affect data binding or generating the control tree, it was non-deterministic whether those changes would actually have an effect, due to the race between the asynchronous operations completing and the page’s lifecycle progressing.

The recommended approach has been and continues to be using the Page.RegisterAsyncTask method with “async Task” methods rather than using “async void”.  This allows the developer to be very clear on their intent, giving ASP.NET a delegate that can be used both to invoke the operation at a specific point and to await the operation’s completion (via the task returned by the delegate), e.g. instead of writing:

async void Button1_Click(object sender, EventArgs e)
{
    … // code that uses await
}

you’d write:

void Button1_Click(object sender, EventArgs e)
{
    RegisterAsyncTask(new PageAsyncTask(async () =>
    {
        … // code that uses await
    }));
}

However, since it was so easy to assume different behavior for “async void” methods, for the Release Candidate, ASP.NET now does allow you to write “async void” methods and have them behave how you expect, aligning better with how asynchronous event handlers behave in UI frameworks like Windows Forms, WPF, and Metro style apps.

Another async-related improvement in ASP.NET shows up on the HttpRequest and HttpResponse objects and has to do with cancellation.  Since ASP.NET’s creation, ASP.NET has supported page timeout, where if a page takes too long to complete (by default, more than 110 seconds), the thread processing the page will be aborted.  This behavior, while well intended and sometimes helpful, has also led to reliability issues due to the dangers of aborting threads at arbitrary locations (ASP.NET has the benefit here that it can spin up new AppDomains and spin down old ones to help work around state corruption problems); it also doesn’t work well with asynchrony, where there may not be a thread to abort.  A more reliable approach to timeouts is via a cooperative cancellation mechanism.  Imagine that ASP.NET provided a CancellationToken that developers could pass around in their code to all cancelable async operations, and then if the page takes too long to complete, that token could have cancellation requested.  This is exactly what ASP.NET now provides in the form of HttpRequest.TimedOutToken.  TimedOutToken returns a CancellationToken that will have cancellation requested if the page takes “too long” to process.

Another related condition occurs when the client that was connected to the server to cause the page to be processed then prematurely disconnects.  In that case, you’d like for asynchronous operations to be able to end early so as not to continue doing work that’s no longer necessary.  In the Release Candidate, ASP.NET now also addresses this case, by providing the HttpResponse.ClientDisconnectedToken property.  ClientDisconnectedToken returns a CancellationToken that will have cancellation requested when the client disconnects.

Dataflow

In the Developer Preview and Beta, the new System.Threading.Tasks.Dataflow.dll library shipped as part of the full .NET Framework package.  It was, however, not available to Metro style apps, and we got feedback from developers that the ability to use the dataflow library in a portable fashion, across both their desktop and Metro style apps, was important.  For the Release Candidate, we’re taking advantage of a new distribution approach from the BCL team, which is to support shipping Framework assemblies on NuGet.  Starting with the Release Candidate, you can now get a portable dataflow library (one that works both with the full .NET Framework and with .NET for Metro style apps) via NuGet.  For more information, see the BCL Team blog.

Conclusion

As always, we continue to march forward thinking a lot about these scenarios and how we can improve them, both from a programmability perspective and from a performance perspective.  If you have any feedback, we’re always interested in hearing it.

Thanks, and enjoy!

Author

Stephen Toub - MSFT
Partner Software Engineer

Stephen Toub is a developer on the .NET team at Microsoft.

0 comments

Discussion are closed.