Async/Await

The short story

// fire and forget
interface IOperation
{
Task Fire();
}
// some consumer code
IOperation operation;
void MyCoolCode(IOperation operation)
{
operation.Fire();
}

Consuming Task based methods

warning CS4014: Because this call is not awaited,
execution of the current method continues before the
call is completed. Consider applying the 'await'
operator to the result of the call.

Treat warnings as errors

// fire and forget
interface IOperation
{
Task Fire();
}
// some consumer code
IOperation operation;
async void MyCoolCode(IOperation operation)
{
await operation.Fire();
}

Fixing our code

Remember

  • Add both async and await
  • void can stay void -> we call this fire and forget
  • void can become Task -> our caller can await us
  • ReturnType becomes Task<ReturnType>
async Task DoStuff(CancellationToken token)
{
await ...
token.ThrowIfCancellationRequested();
for ()
{
await ...
token.ThrowIfCancellationRequested();
}
}

Always check cancellation!

try
{
await...
}
catch (OperationCancelledException)
{
// cancelled
}
catch
{
// errored!
}
* >

Keep in mind

  • async is infectious - convert all callers and their callers until the root
  • Always try-catch in the void root

The end?

The long version

Also known as details

Agenda

  • How did we get here?
  • Patterns and pitfalls
  • Exception handling
  • Combinators
  • Link to Rx and IAsyncEnumerable
  • UX problems
  • Conclusion

C# is introduced to the world

2002
var file = File.Create("out.txt");
var bytes = Encoding.UTF8.GetBytes("hello world");
file.BeginWrite(bytes, 0, bytes.Length, ar =>
{
file.EndWrite(ar);
}, null);

Simple async patterns

When do you close a file?

var file = File.Create("out.txt");
var bytes = Encoding.UTF8.GetBytes("hello world");
file.BeginWrite(bytes, 0, bytes.Length, ar =>
{
file.EndWrite(ar);
// Do it here
file.Close();
}, null);

The end is in the callback

Asynchronous Programming Model

  • Begin/End pair of methods
  • Consumes thread -> thread pool starvation
  • Alternative is to poll the result for IsCompleted
  • Doesn't compose well with other language constructs
  • Impossible to orchestrate
  • Read more at Asynchronous Programming Model (APM)
using (var file = File.Create("out.txt"))
{
var bytes = Encoding.UTF8.GetBytes("hello world");
file.BeginWrite(bytes, 0, bytes.Length, ar =>
{
file.EndWrite(ar);
}, null);
}

This doesn't work!

// write them in exact order!
foreach (var fileName in fileNames)
{
var bytes = Encoding.UTF8.GetBytes("hello world");
file.BeginWrite(bytes, 0, bytes.Length, ar =>
{
file.EndWrite(ar);
}, null);
}

Also doesn't work!

Moving on to the future! .NET Framework 2

2005
var client = new WebClient();
client.DownloadStringAsync(new Uri("https://e-conomic.com"));
client.DownloadStringCompleted += Client_DownloadStringCompleted;
private void Client_DownloadStringCompleted(
object sender,
DownloadStringCompletedEventArgs e)
{
var html = e.Result;
}

Events!

Event-based Asynchronous Pattern

  • More modern
  • Doesn't consume threads
  • Allows multiple listeners
  • Returns on the original thread
  • Still doesn't compose
  • If the owner of waiting wants to be async, must implement similar interface
  • Read more at Event-based Asynchronous Pattern (EAP)

Is there something better?

  • We want easy to use interface
  • Writing owners of async operation should also be easy to do
  • Cancellation should be easy to use
  • Progress reporting should be easy to do
  • Should compose well with C# constructs

Task Parallel Library - functional programming to the rescue!

2010
var task = GetMeSomeTask();
task.ContinueWith(t =>
{
var result = t.Result;
});

New async pattern

var client = new WebClient();
var task = client.DownloadStringTaskAsync(...);
task.ContinueWith(t =>
{
var html = t.Result;
});

Switching from EAP to TPL

Is this really better?

Spoiler: it is!
var client = new WebClient();
Task<string> task = client.DownloadStringTaskAsync(...);
Task<int> length = task.ContinueWith(t =>
{
var html = t.Result;
return html.Length;
});

Tasks are a consistent interface

var client = new WebClient();
var task = client.DownloadStringTaskAsync(...);
return task
.ContinueWith(t =>
{
var html = t.Result;
return html.Length;
})
.ContinueWith(t =>
{
var length = t.Result;
// be creative here
});

It is easy to chain them

var promise = getData()
.then(onSuccess)
.catch(onFail);

Promises are tasks

  • to cancel throw OperationCancelledException
  • one can schedule to run on success, failure, cancellation or any combo
  • one can schedule to run on any thread or on the UI thread

What is a Task?

  • a Task represents an ongoing operation (but it may already be when you get it)
  • continuations are used to subscribe to the result
  • subscribing after it is finished immediately invokes the continuation
  • same for cancelled/faulted tasks
  • IO bound and CPU bound operations

Hmmm...

  • We still haven't solved all of the problems
  • Hard to compose with C# constructs
  • We are introducing CancellationToken - cooperative cancellation
  • For progress use IProgress<T> - manual work, but still...

OK, let's do it manually!

public class Downloader : IDisposable
{
private HttpClient client;
public Downloader()
{
this.client = new HttpClient();
}
public void Dispose()
{
client.Dispose();
}
public Task<string> Fetcher(string subpath,
CancellationToken cancellationToken)
{
return client.GetStringAsync("baseUri" + subpath)
.ContinueWith(t =>
{
cancellationToken.ThrowIfCancellationRequested();
return t.Result;
});
}
}

Wrapper manages C# constructs

// Challenge! Convert to async
public static void Copy(Stream a, Stream b)
{
var bytes = new byte[32];
var offset = 0;
int count;
while ((count = a.Read(bytes, offset, 32)) > 0)
{
b.Write(bytes, offset, count);
offset += count;
}
}

Simple sync code

  • we could rewrite it to async - but it's hard
  • lot's of variables, state
  • potential bugs
  • non-obvious
  • is anyone good at rewriting code?
public Task DoAsync()
{
// some code
int i = 0;
task
.ContinueWith(t =>
{
// some code that uses result...
i = 1;
})
.ContinueWith(t =>
{
// some code that uses second result...
i = -1;
});
}

Hmmmm

public Task DoAsync()
{
// some code
int i = 0;
var result1 <== task;
// some code that uses result...
i = 1;
var result2 <== task2;
// some code that uses second result...
i = -1;
}

Assuming happy flow

public async Task DoAsync()
{
// some code
int i = 0;
var result1 = await task;
// some code that uses result...
i = 1;
var result2 = await task2;
// some code that uses second result...
i = -1;
}

That was easy!

  • async function is a state machine, but better than hand-written one
  • it forces the method to return void, Task or Task<T>
  • and now it is composable with C#
  • cannot be used inside unsafe, lock and constructors

Myth busting

  • Async != parallel
  • Async != threads
  • Async means not-sync

What's next?

  • ConfigureAwait(false)
  • Exception handling
  • Combinators

await saves current thread by default!

  • required by UI applications
  • wastes threads in ASP.NET workloads and in non-UI scenarios
  • ConfigureAwait(false) tells the app to not remember the thread
  • not needed in ASP.NET Core
  • Read more AspNetCoreDiagnosticScenarios
  • use it always or never; if you aren't writing libraries, don't bother
// I don't care about thread
async Task Foo() => await Inner().ConfigureAwait(false);
// I don't care, but forgot to tell that!
async Task Inner() => await DeepCall();
// library also doesn't care about thread
async Task DeepCall() => await WeMustGoDeeper().ConfigureAwait(false);

Why all participants need to use it

Exceptions are saved in Tasks

  • void root handlers should always catch!
  • otherwise TaskScheduler.UnobservedTaskException will be invoked which won't crash your app since .NET 4.5 (but you can change it)
  • awaiting already faulted Task immediately throws the inner exception.

CPU bound stuff

  • When dealing with heavy CPU operations use Task.Run
  • Better than background worker or threads for short-lived code
  • Freely mix fake tasks, IO bound tasks and CPU bound tasks
  • Read more Task.Run vs Task.Factory.StartNew
var result = await Task.Run(AnalyzeData);

Run heavy code...

var result = await Task.Run(async () =>
{
await Task.Delay(0);
return 1;
});
var result = await Task.Factory.StartNew(async () =>
{
await Task.Delay(0);
return 1;
}).Unwrap();

DEMO fake task

Combinators

  • Task.WhenAll and Task.WhenAny
  • latter is useful for timeout
public static Task<T> WithTimeout<T>(this Task<T> task, int timeout)
{
var t = Task.Delay(timeout);
var first = Task.WhenAny(task, t);
if (first == t)
throw new Exception();
return Task.FromResult(task.Result);
}

Generic extension method on all tasks!

HACK HACK HACK

  • We can force async operation to finish synchronously by calling .Result, .Wait() or preferably .GetAwaiter().GetResult().
  • But please don't! Only for legacy code where rewriting the entire stack is dangerous
  • In certain cases this might deadlock

sync vs async

syncasync
oneTTask<T>
manyIEnumerable<T>IObservable<T>
  • Rx = LINQ to events
  • if Task gives one result IObservable gives many
private async void Button_Click_3(object sender, RoutedEventArgs e)
{
var client = new HttpClient();
await client.GetStringAsync("https://secure.e-conomic.com");
}

Motivating example - throttling async operations

Task throttle = null;
CancellationTokenSource cts = new CancellationTokenSource();
private void Button_Click_3(object sender, RoutedEventArgs e)
{
if (throttle != null)
{
cts.Cancel();
};
cts = new CancellationTokenSource();
throttle = Task.Delay(500, cts.Token);
throttle.ContinueWith(
t => Download(),
TaskContinuationOptions.OnlyOnRanToCompletion);
}
async Task Download()
{
var client = new HttpClient();
await client.GetStringAsync("https://secure.e-conomic.com");
}

Complicated, but can be made as a higher order operation

IAsyncEnumerable

  • for and await are opaque -> they become Task<TResult>
  • but maybe the consumer also want's to for-await
Task<IEnumerable<T>> list;
foreach (var element in await list)
{
// only runs once everything is retrieved!
}

How to do SelectAsync

static async Task Main(string[] args)
{
await foreach (var html in Downloader(
"http://e-conomic.com",
"http://secure.e-conomic.com"))
{
Console.WriteLine($"HTML: {html.Substring(0, 10)}");
}
}
static async IAsyncEnumerable<string> Downloader(params string[] urls)
{
foreach (var url in urls)
{
Console.WriteLine($"Fetching {url}");
yield return await new HttpClient().GetStringAsync(url);
}
}

Truly async enumerables

The bad part

  • Only in C#8, .NET Standard 2.1 and .NET Core 3.0
  • Possible in ASP.NET but please don't!

Onto the next part

  • UX issues

Thanks! Q&A