This is the Async / Await feature added to .NET in .NET 4.5, specifically...Everything I Wish I Knew When I Started Using It! By avoiding the client side discussions around the UI and parallel processing, we can focus on the environment in which most of us live and have both an introduction and deeper dive into how it all works. This is about how we can all use the feature RIGHT NOW to write better performing code.
2. So…what’s this about?
Task-based Asynchronous Pattern on the server
What is it?
Why use it?
How does it work?
Or…everything I wish I knew about async/await when I started using it!
3. Life is Asynchronous!
Cooking a Spaghetti Dinner
Fill pot with water
Put pot on stove
Start boiling water (async)
Do dishes
When water is boiling… (await)
Put spaghetti in boiling water (async)
Start warming up pasta sauce (async)
Make salad
Pour drinks
When spaghetti and sauce finished… (await)
Make plates of spaghetti with sauce and side salad
4. Synchronous Cooking!
Fill pot with water
Put pot on stove
Wait for water to boil (just stare at it)
Put spaghetti in pot
Wait for spaghetti to cook (stare harder)
Strain spaghetti
Fill another pan with pasta sauce
Put pasta sauce on stove
Wait for pasta sauce to warm up (keep staring…maybe it helps?)
Make salad
Pour drinks
Make plates of spaghetti with sauce and side salad
6. This is not about…
Other forms of concurrency in the Task Parallel Library (TPL)
Data Parallelism (Parallel.For and ForEach)
Task Parallelism (Tasks without async/await)
Parallel LINQ (PLINQ)
TPL Dataflow
7. Process – High Level
An instance of a program running on a computer
In IIS, a w3wp.exe worker process runs for every
app pool you have running
NOT for every web application
*Side Note* Running multiple worker processes for
same web application called a Web Garden
8. Thread – High Level
By this I mean .NET CLR MANAGED threads
1 or more threads to a process
1 Megabyte memory per thread stack reservation size (by default)
Expensive to allocate and garbage collect
Multi-Core compatible
Using multiple likely means using multiple cores
Threads distributed amongst CPUs by OS
9. Threadpool – High Level
A “pool” or collection of .NET managed threads
Background worker threads managed by the system
For quick work without the overhead of allocating a new thread
Only 1 threadpool per process
10. Async in ASP.NET video - Levi Broderick
IIS requests – High Level
In ASP.NET, AVOID using thread pool threads
11. Avoid Threadpool usage in ASP.NET
Request made in ASP.NET uses a threadpool thread
That includes any mechanisms that use threadpool threads
Basically all compute bound parallel processing
PLINQ
Task.Run
Task.Factory.StartNew
Parallel.For
12. Promises and Futures
Promise - a writable, single assignment container which sets the value of the
future (via .SetResult in C#)
C# - TaskCompletionSource
Java – SettableFuture
jQuery - $.Deferred()
AngularJs - $q.defer()
ES6 – don’t have direct access, but call resolve or reject within passed in function
Future - read-only placeholder view of a variable
C# - Task or Task<T>
Java - Future
jQuery - $.Deferred().promise
AngularJs - $q.defer().promise
ES6 - new Promise( function (resolve, reject) { ... })
13. What is async/await?
Asynchronous programming made easy!
Almost as easy to do async as it is to do synchronous programming
Ties in to Task Parallel Library’s Task functionality
A new language feature in .NET 4.5
Async/Await released with C# 5.0 (.NET 4.5), released August 2012
Can compile using VS 2012+
14. Why async/await?
For network I/O
Web service calls
Database calls
Cache calls
Any call to any other server
Something else doing the work
Computationally intensive work using Task.Run (avoid in ASP.NET)
It is doing the work
15. What methods can be async?
Methods with the following return types can be made async
Task
Task<T>
void //but avoid it!
async Task
async Task<T>
async void
16. Task – High Level
Multi-core compatible
Unit of Work
POTENTIALLY asynchronous operation
17. Tasks are like IOUs…
Task t = GetTaskAsync();
Active task when not…
t.IsCompleted
t.IsFaulted
t.IsCanceled
18. Synchronous Method
ASYNChronous Method
private async Task<string> GetUrlAsync(string url)
{
using (var client = new HttpClient())
{
return await client.GetStringAsync(url); //network I/O, thread not blocking
}
}
private string GetUrl(string url)
{
using (var client = new WebClient())
{
return client.DownloadString(url);
}
}
19. What does the async keyword do?
Lets the method know that it can have await keyword
Tells the method to wrap the returned value in a Task
Tells the compiler to generate a LOT of code (another slide)
It’s an implementation detail
public interface IHttpService
{
Task<string> GetUrlAsync(string url);
}
public class HttpService : IHttpService
{
public async Task<string> GetUrlAsync(string url)
{
string result;
using (var client = new HttpClient())
{
result = await client.GetStringAsync(url); //network I/O
}
return result;
}
}
20. So…why use it?
private static readonly List<string> Urls = new List<string>
{
"http://blogs.msdn.com/b/pfxteam/",
"http://blog.stephencleary.com/",
"https://channel9.msdn.com/",
"http://www.pluralsight.com/",
"http://stackoverflow.com/"
};
[HttpGet]
[Route("geturlssync")]
public string GetUrls()
{
var watch = Stopwatch.StartNew();
var urlResults = new List<string>();
foreach (string url in Urls)
{
urlResults.Add(GetUrl(url));
}
//LINQ select via method group syntax
//var urlResults = Urls.Select(GetUrl);
return watch.ElapsedMilliseconds.ToString();
}
Test took 3.2 seconds to run
21. So…why use it? cont’d
[HttpGet]
[Route("geturlsasync")]
public async Task<string> GetUrlsAsync()
{
var watch = Stopwatch.StartNew();
var urlResultsTasks = new List<Task<string>>();
foreach (string url in Urls)
{
urlResultsTasks.Add(GetUrlAsync(url));
}
//LINQ select via method group syntax
//var urlResultsTasks = Urls.Select(GetUrlAsync);
await Task.WhenAll(urlResultsTasks);
return watch.ElapsedMilliseconds.ToString();
}
Test took 1.5 seconds to run AND fewer server resources
24. Task.Delay
Replacement for Thread.Sleep
Returns completed task after a specified delay
await Task.Delay(1000);
Don’t use Thread.Sleep with async tasks!
25. WhenAny – more advanced
[HttpGet]
[Route("getfromcacheordb")]
public async Task<string> GetFromCacheOrDb()
{
string retVal = null;
var getFromCacheTask = GetFromCacheAsync();
await Task.WhenAny(getFromCacheTask, Task.Delay(2000));
if (getFromCacheTask.IsCompleted)
{
retVal = await getFromCacheTask;
//perfectly safe to use getFromCacheTask.Result here
//but I won't…see the DANGER ZONE section
}
else
{
var getFromDbTask = GetFromDbAsync();
var taskWithData = await Task.WhenAny(getFromCacheTask, getFromDbTask);
retVal = await taskWithData;
}
return retVal;
}
27. Generated Async Code cont’d
[CompilerGenerated]
[StructLayout(LayoutKind.Auto)]
private struct u003CGetUrlAsyncu003Ed__0 : IAsyncStateMachine
{
public int u003Cu003E1__state;
public AsyncTaskMethodBuilder<string> u003Cu003Et__builder;
public HttpService u003Cu003E4__this;
public string url;
public string u003Cresultu003E5__1;
public HttpClient u003Cclientu003E5__2;
private TaskAwaiter<string> u003Cu003Eu__u0024awaiter3;
private object u003Cu003Et__stack;
void IAsyncStateMachine.MoveNext()
{
string result1;
try
{
bool flag = true;
switch (this.u003Cu003E1__state)
{
case -3:
break;
case 0:
try
{
TaskAwaiter<string> awaiter;
if (this.u003Cu003E1__state != 0)
{
awaiter = this.u003Cclientu003E5__2.GetStringAsync(this.url).GetAwaiter();
if (!awaiter.IsCompleted)
{
this.u003Cu003E1__state = 0;
this.u003Cu003Eu__u0024awaiter3 = awaiter;
this.u003Cu003Et__builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>, HttpService.u003CGetUrlAsyncu003Ed__0>(ref awaiter, ref this);
flag = false;
return;
}
}
else
{
awaiter = this.u003Cu003Eu__u0024awaiter3;
this.u003Cu003Eu__u0024awaiter3 = new TaskAwaiter<string>();
this.u003Cu003E1__state = -1;
}
string result2 = awaiter.GetResult();
TaskAwaiter<string> taskAwaiter = new TaskAwaiter<string>();
this.u003Cresultu003E5__1 = result2;
}
finally
{
if (flag && this.u003Cclientu003E5__2 != null)
this.u003Cclientu003E5__2.Dispose();
}
result1 = this.u003Cresultu003E5__1;
break;
default:
this.u003Cclientu003E5__2 = new HttpClient();
goto case 0;
}
}
catch (Exception ex)
{
this.u003Cu003E1__state = -2;
this.u003Cu003Et__builder.SetException(ex);
return;
}
this.u003Cu003E1__state = -2;
this.u003Cu003Et__builder.SetResult(result1);
}
[DebuggerHidden]
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine param0)
{
this.u003Cu003Et__builder.SetStateMachine(param0);
}
}
28. SynchronizationContext
.NET applications have a synchronization context
It’s different for each type of app, but fall into buckets
ASP.NET
MVC
WebAPI
WebForms
UI
WPF
WinForms
Windows Store app
Neither
Console app
29. SynchronizationContext suggestions
If it’s on the UI or in ASP.NET and you don’t need the context…
don’t continue on captured context
public async Task<string> GetUrlAsync(string url)
{
string result = await GetUrlAsync(url, CancellationToken.None).ConfigureAwait(false);
return result;
}
30. [HttpGet]
[Route("getrequesturl")]
public async Task<string> GetRequestUrl()
{
await DoingSomethingAsync().ConfigureAwait(continueOnCapturedContext: false);
// And now we're on the thread pool thread without a captured context
//so the HttpContext.Current is null
//so...UNHANDLED EXCEPTION without compiler warning!
return HttpContext.Current.Request.RawUrl;
}
SynchronizationContext suggestions cont’d
But there are places where you do need the context…
31. My rule of thumb:
Don’t use .ConfigureAwait(false) on endpoints and ASP.NET pipeline
MVC controller actions
WebAPI actions
Filters
HttpHandlers
Http Message Handlers like DelegatingHandler
Use .ConfigureAwait(false) basically everywhere else
SynchronizationContext suggestions cont’d
34. The Deadlock with .Result or .Wait()
Don’t do .Result or .Wait() on unfinished task
Or at all…
[HttpGet]
[Route("deadlockdemo")]
public string DeadlockDemo()
{
string urlContent = _httpService.GetUrlAsync("http://finance.yahoo.com").Result;
return urlContent;
}
public async Task<string> GetUrlAsync(string url)
{
string result = await GetUrlAsync(url, CancellationToken.None);
return result;
}
AspNetSynchronizationContext will ensure that they execute one at a time
https://msdn.microsoft.com/en-us/magazine/gg598924.aspx
36. The Deadlock cont’d
Remember the SynchronizationContext?
[HttpGet]
[Route("deadlockdemo")]
public async Task<string> DeadlockDemo()
{
string urlContent = _httpService.GetUrlAsync("http://finance.yahoo.com").Result;
return urlContent;
}
public async Task<string> GetUrlAsync(string url)
{
string result = await GetUrlAsync(url, CancellationToken.None).ConfigureAwait(false);
return result;
}
If ALL awaited tasks are set to configureawait false, the block won’t cause a deadlock
37. Synchronous Method
public string Echo(string message)
{
return message;
}
ALSO Synchronous Method
public async Task<string> EchoAsync(string message)
{
return message;
}
10x SLOWER!
38. ALSO Synchronous
public Task<string> EchoAsync2(string message)
{
return Task.FromResult(message);
}
10x SLOWER!
2.5x SLOWER!
public async Task<string> EchoAsync3(string message)
{
return await Task.FromResult(message);
}
39. Asynchronous, but in Parallel
public async Task<string> GetUrlAsync2()
{
string result = await Task.Run(async () =>
await _httpService.GetUrlAsync("http://blogs.msdn.com/b/pfxteam/")
);
return result;
}
Already an async call, no need for parallel
41. But I need to call an async method
synchronously!
IF you can’t change the interface
Can’t change from return of string to return of Task<string> that gets awaited
[HttpGet]
[Route("asyncassync")]
public string AsyncAsSync()
{
string cachedItem = Task.Run(() => GetFromCacheAsync(CancellationToken.None)).Result;
return cachedItem;
}
Avoid if at all possible!
42. Async void
Avoid async void!
Only use async void if you MUST conform to prior interface.
Primarily for events
private async void btnDownload_Click(object sender, RoutedEventArgs e)
{
btnDownload.IsEnabled = false;
try
{
txtResult.Text = await DownloadStringAsync(txtUrl.Text,
new Progress<int>(p => pbDownloadProgress.Value = p));
}
finally { btnDownload.IsEnabled = true; }
}
43. Async All The Way
Once you start, you can’t stop…
public static async Task<T> MakeHttpRequestAsync<T>(…)
private async Task<TResult> GetHttpAsync<T, TResult>(…)
public async Task<TResponse> GetRulesEngineResponseAsync(…)
private async Task<SavingsRulesResponse> GetSavingsRulesAsync(…)
public async Task<ExperimentTemplate> CallToActionBuilderAsync(…)
private Task GenerateCallsToActionAndRespondAsync(…)
[System.Web.Http.Route("generatecallstoaction")]
[HttpPost]
public async Task<ApiResponseActionResult> GenerateCallsToActionAsync(…)
44. Back to the regularly scheduled talk…
using System.Threading.Tasks;
45. Cancellations
[HttpGet]
[Route("geturlsasync")]
public async Task<string> GetUrlsAsync()
{
var watch = Stopwatch.StartNew();
var cts = new CancellationTokenSource(1000);
var urlResultsTasks = new List<Task<string>>();
try
{
urlResultsTasks = Urls.Select(url => _httpService.GetUrlAsync(url, cts.Token)).ToList();
await Task.WhenAll(urlResultsTasks);
}
catch (TaskCanceledException ex)
{
//swallow ex for now
}
return string.Format("Cancelled Tasks: {0} Elapsed Time In MS: {1}",
urlResultsTasks.Count(x => x.IsCanceled), watch.ElapsedMilliseconds);
}
OUTPUT: "Cancelled Tasks: 1 Elapsed Time In MS: 1152"
46. Cancellations cont’d
The trickle down effect of cancellation code
public async Task<string> GetUrlAsync(string url,CancellationToken cancellationToken)
{
string result;
var uri = new Uri(url);
using (var client = new HttpClient())
{
cancellationToken.ThrowIfCancellationRequested(); //optional
var response = await client.GetAsync(uri, cancellationToken); //network I/O
cancellationToken.ThrowIfCancellationRequested(); //optional
result = await response.Content.ReadAsStringAsync(); //potentially network I/O
}
return result;
}
47. Cancellations linked
[HttpGet]
[Route("geturlsasync2")]
//[AsyncTimeout(1500)] for MVC only
public async Task<string> GetUrlsAsync2(CancellationToken cancellationToken)
{
var watch = Stopwatch.StartNew();
var cts = new CancellationTokenSource(1000);
var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, cts.Token);
var urlResultsTasks = new List<Task<string>>();
try
{
urlResultsTasks = Urls.Select(url => _httpService.GetUrlAsync(url, linkedCts.Token)).ToList();
await Task.WhenAll(urlResultsTasks);
}
catch (TaskCanceledException ex)
{
//swallow ex for now
}
return string.Format("Cancelled Tasks: {0} Elapsed Time In MS: {1}",
urlResultsTasks.Count(x => x.IsCanceled), watch.ElapsedMilliseconds);
}
48. Async lambdas
Can use async on Action<T> and Func<T> types
[HttpGet]
[Route("asynclambda")]
public async Task<string> AsyncLambda()
{
string closureMessage = null;
var task = ExecuteAsyncAndLogTime("GetFromCacheAsync", async () =>
{
closureMessage = await GetFromCacheAsync(CancellationToken.None);
});
await task;
return closureMessage;
}
private async Task ExecuteAsyncAndLogTime(string title, Func<Task> asyncAction)
{
var watch = Stopwatch.StartNew();
await asyncAction.Invoke();
Debug.WriteLine("{0} ElapsedTimeInMs: {1}", title, watch.ElapsedMilliseconds);
}
49. Async lambdas cont’d
The Try Tri!
public static async Task<T> DoAsync<T>(Func<Task<T>> action, TimeSpan retryInterval,
int retryCount = 3)
{
var exceptions = new List<Exception>();
for (int retry = 0; retry < retryCount; retry++)
{
if (retry > 0)
{
await Task.Delay(retryInterval);
}
try
{
return await action();
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
50. Task.FromResult
[HttpGet]
[Route("fromresult")]
public async Task<string> FromResult()
{
string closureMessage = null;
var task = ExecuteAsyncAndLogTime("Synchronous",() =>
{
closureMessage = GetText();
return Task.FromResult(0);
//.NET 4.6 has Task.CompletedTask for this
});
await task;
return closureMessage;
}
51. Hot and Cold methods
All async tasks run HOT
As soon as task created, method is started (like regular method)
[HttpGet]
[Route("getfromcache")]
public async Task<string> GetFromCache()
{
var getFromCacheTask = GetFromCacheAsync(CancellationToken.None);
SomethingSynchronous();
string cachedItem = await getFromCacheTask;
return cachedItem;
}
Note: GetFromCacheAsync takes 0.5 seconds and SomethingSynchronous takes 1 second
Total time was 1.149 seconds
52. Cold Tasks (.NET 4.0)
[HttpGet]
[Route("coldtask")]
public void ColdTask()
{
var task = new Task(() =>
{
SomethingSynchronous();
// ...and some more stuff
});
task.Start();
}
If you have a need, could be useful for computationally bound asynchrony, but…
You should probably AVOID this
53. Exceptions
[HttpGet]
[Route("throwerror")]
public async Task<string> ThrowErrorAsync()
{
string message = null;
var t = ThrowErrorCoreAsync();
try
{
message = await t;
}
catch (Exception ex)
{
return "ERROR awaiting";
}
return message;
}
private async Task<string> ThrowErrorCoreAsync()
{
throw new Exception("Error!");
await GetFromDbAsync(CancellationToken.None);
}
Work basically how you’d expect…
54. Exceptions cont’d
Well…almost
private async Task GetMultipleErrorsAsync()
{
var t1 = ThrowErrorCoreAsync();
var t2 = ThrowErrorCoreAsync2();
var t3 = ThrowErrorCoreAsync3();
var tAll = Task.WhenAll(t1, t2, t3);
try
{
await tAll;
}
catch (Exception ex)
{
Debugger.Break();
}
}
Exception thrown is error from t1, but tAll has AggregateException of all 3 errors
55.
56. Aync Http
public async Task<string> GetUrlAsync(string url,CancellationToken cancellationToken)
{
string result;
var uri = new Uri(url);
using (var client = new HttpClient())
{
var response = await client.GetAsync(uri, cancellationToken).ConfigureAwait(false); //network I/O
result = await response.Content.ReadAsStringAsync().ConfigureAwait(false); //potentially network I/O
}
return result;
}
58. Service References cont’d
public static async Task Foo()
{
using (ServiceReference1.Service1Client client = new ServiceReference1.Service1Client())
{
Task<string> t = client.GetDataAsync(1);
string result = await t;
}
}
59. ADO.NET
using (SqlConnection connection = new SqlConnection(connectionString))
{
await connection.OpenAsync();
using (SqlCommand command = new SqlCommand(commandString, connection))
{
using (SqlDataReader reader = await command.ExecuteReaderAsync())
{
//Some code
if (await reader.ReadAsync())
{
//more code
}
//Other code
}
}
}
60. Entity Framework 6
public static async Task PerformDatabaseOperations()
{
using (var db = new BloggingContext())
{
// Create a new blog and save it
db.Blogs.Add(new Blog
{
Name = "Test Blog #" + (db.Blogs.Count() + 1)
});
Console.WriteLine("Calling SaveChanges.");
await db.SaveChangesAsync();
Console.WriteLine("SaveChanges completed.");
// Query for all blogs ordered by name
Console.WriteLine("Executing query.");
var blogs = await (from b in db.Blogs
orderby b.Name
select b).ToListAsync();
// Write all blogs out to Console
Console.WriteLine("Query completed with following results:");
foreach (var blog in blogs)
{
Console.WriteLine(" - " + blog.Name);
}
}
}
61. EF 6 stored procedure async
with Table-Valued Parameter
public async Task UpdateSoftOfferEndpointsActiveFlag(Dictionary<string, bool> softOfferEndpointDictionary)
{
const string sql = "exec [SavingsEngine].[pUpdateSoftOfferEndpointsActiveFlag]
@ipSoftOfferEndpointDictionary";
if (softOfferEndpointDictionary == null || !softOfferEndpointDictionary.Any())
{
return;
}
var ipSoftOfferEndpointDictionaryParameter = new SqlParameter("@ipSoftOfferEndpointDictionary",
System.Data.SqlDbType.Structured) { TypeName = "SavingsEngine.DictionaryStringBit" };
var temp = new System.Data.DataTable();
temp.Columns.Add("Key", typeof(string));
temp.Columns.Add("Value", typeof(bool));
foreach (var kvp in softOfferEndpointDictionary)
{
temp.Rows.Add(kvp.Key, kvp.Value);
}
ipSoftOfferEndpointDictionaryParameter.Value = temp;
await _cbasContext.Database.ExecuteSqlCommandAsync(sql,
ipSoftOfferEndpointDictionaryParameter).ConfigureAwait(false);
}
63. MongoDB
https://github.com/mongodb/mongo-csharp-driver
var client = new MongoClient("mongodb://localhost:27017");
var database = client.GetDatabase("foo");
var collection = database.GetCollection<Person>("bar");
await collection.InsertOneAsync(new Person { Name = "Jack" });
var list = await collection.Find(x => x.Name == "Jack") .ToListAsync();
foreach(var person in list)
{
Console.WriteLine(person.Name);
}
64. Amazon Web Services
http://docs.aws.amazon.com/sdkfornet/latest/apidocs/Index.html
Simple Notification Service (SNS)
public virtual Task<PublishResponse> PublishAsync( PublishRequest
request, CancellationToken cancellationToken )
Simple Queue Service (SQS)
public virtual Task<SendMessageResponse> SendMessageAsync(
SendMessageRequest request, CancellationToken cancellationToken)
Simple Storage Service (S3)
public virtual Task<GetObjectResponse> GetObjectAsync(
GetObjectRequest request, CancellationToken cancellationToken )
65. New in .NET 4.6
Not much…
Task.FromException
Task.FromCancelled
Task.CompletedTask
Task{Creation/Continuation}Options.RunContinuationsAsynchronously
Allows you to run .SetResult within a lock
Except…
Now able to await in catch and finally blocks!
66. Conclusion
Code the way you live, asynchronously!
That is, code NETWORK I/O calls asynchronously
But…
Don’t use async on synchronously running code
Test the performance. Is it saving time?
68. Why run this as async?
[HttpGet]
[Route("geturlasync")]
public async Task<string> GetUrlAsync()
{
string result = await _httpService.GetUrlAsync("http://blogs.msdn.com/b/pfxteam/");
return result;
}
There is no thread!
When the thread has no more work to do while awaiting, it goes back to the threadpool
Increased server throughput
Server can process more requests
69. Task.Yield
Copied from TAP doc
https://www.microsoft.com/en-us/download/details.aspx?id=19957
Task.Run(async delegate
{
for(int i=0; i<1000000; i++)
{
await Task.Yield(); // fork the continuation into a separate work item
...
}
});
70. public static Task<string> DownloadStringAsync(Uri url)
{
var tcs = new TaskCompletionSource<string>();
var wc = new WebClient();
wc.DownloadStringCompleted += (s,e) =>
{
if (e.Error != null) tcs.TrySetException(e.Error);
else if (e.Cancelled) tcs.TrySetCanceled();
else tcs.TrySetResult(e.Result);
};
wc.DownloadStringAsync(url);
return tcs.Task;
}
TaskCompletionSource and EAP
Copied from TAP doc
https://www.microsoft.com/en-us/download/details.aspx?id=19957
71. Meet the experts
Stephen Toub
On Visual Studio Parallel Programming team at
Microsoft
http://blogs.msdn.com/b/pfxteam/
Wrote THE document on the TAP
https://www.microsoft.com/en-
us/download/details.aspx?id=19957
Speaker at MS Build 2011,2013
72. Meet the experts cont’d
Stephen Cleary
Microsoft MVP
Avid StackOverflow user
Answered my async/await
question
Great blog
http://blog.stephencleary.com/
Wrote the book on concurrency
Concurrency in C# Cookbook on
Amazon
73. References
The Task-based Asynchronous Pattern
The zen of async: Best practices for best performance
MS Build 2011 video – Stephen Toub
Async in ASP.NET video with Damian Edwards et al.
Async'ing Your Way to a Successful App with .NET
MS Build 2013 video – Stephen Toub
Building parallelized apps with .NET and Visual Studio
MS Build 2011 video – Stephen Toub
Stephen Toub: Task-Based Asynchrony with Async