Is Async Await Keyword Equivalent to a Continuewith Lambda

Is Async await keyword equivalent to a ContinueWith lambda?

The general idea is correct - the remainder of the method is made into a continuation of sorts.

The "fast path" blog post has details on how the async/await compiler transformation works.

Differences, off the top of my head:

The await keyword also makes use of a "scheduling context" concept. The scheduling context is SynchronizationContext.Current if it exists, falling back on TaskScheduler.Current. The continuation is then run on the scheduling context. So a closer approximation would be to pass TaskScheduler.FromCurrentSynchronizationContext into ContinueWith, falling back on TaskScheduler.Current if necessary.

The actual async/await implementation is based on pattern matching; it uses an "awaitable" pattern that allows other things besides tasks to be awaited. Some examples are the WinRT asynchronous APIs, some special methods such as Yield, Rx observables, and special socket awaitables that don't hit the GC as hard. Tasks are powerful, but they're not the only awaitables.

One more minor nitpicky difference comes to mind: if the awaitable is already completed, then the async method does not actually return at that point; it continues synchronously. So it's kind of like passing TaskContinuationOptions.ExecuteSynchronously, but without the stack-related problems.

TaskContinuations ran synchronously with async lambdas

I second Marc's comment: you should avoid ContinueWith; it's a low-level API that has potentially dangerous default behavior.

The part after the await - will it effectively release the thread that started executing the task continuation?

The await will do that, yes.

And on which thread will the rest of this continuation resume? ... The question is asked when there is no SynchronizationContext present for the antecedent task of the continuation above

Most likely, it would be the thread pool thread that completes the Task returned by Task.Delay. This is because await uses TaskContinuationOptions.ExecuteSynchronously (note: this is an implementation detail; do not depend on this behavior).

I say "most likely" because there are situations where ExecuteSynchronously doesn't execute synchronously. It's an optimization hint, not a requirement. This also applies to the part of your continuation before the await: it is most likely (not definitely) running on the thread that completed the antecedent task.

but I've heard that SynchronziationContext does not flow through ContinueWith method calls? Is that true?

ContinueWith works at the task level. It has some logic for flowing through TaskScheduler.Current (which IMO is one aspect that makes this API dangerous), but has no awareness of SynchronizationContext.

Difference between await and ContinueWith

In the second code, you're synchronously waiting for the continuation to complete. In the first version, the method will return to the caller as soon as it hits the first await expression which isn't already completed.

They're very similar in that they both schedule a continuation, but as soon as the control flow gets even slightly complex, await leads to much simpler code. Additionally, as noted by Servy in comments, awaiting a task will "unwrap" aggregate exceptions which usually leads to simpler error handling. Also using await will implicitly schedule the continuation in the calling context (unless you use ConfigureAwait). It's nothing that can't be done "manually", but it's a lot easier doing it with await.

I suggest you try implementing a slightly larger sequence of operations with both await and Task.ContinueWith - it can be a real eye-opener.

Now that we have an await keyword, is there any benefit to using ContinueWith method?

The second snippet has no benefits, doesn't "save" any threads while allocating another task object and making debugging and exception handling harder by wrapping any exceptions in an AggregateException.

A task is a promise that something will produce some output in the future. That something may be :

  • A background operation running on a threadpool thread
  • A network/IO operation that doesn't run on any thread, waiting instead for the Network/IO driver to signal that the IO has finished.
  • A timer that signals a task after an interval. No kind of execution here.
  • A TaskCompletionSource that gets signalled after some time. No execution here either

HttpClient.GetAsync, HttpClient.GetStringAsync or Content.ReadAsStringAsync are such IO operations.

await doesn't make anything run asynchronously. It only awaits already executing tasks to complete without blocking.

Nothing is gained by using ContinueWith the way the second snippet does. This code simply allocates another task to wrap the task returned by ReadAsStringAsync. .Result returns the original task.

Should that method fail though, .Result will throw an AggregateException containing the original exception - or is it an AggregateException containing an AggregateException containing the original? I don't want to find out. Might as well have used Unwrap(). Finally, everything is still awaited.

The only difference is that the first snippet returns in the original synchronization context after each await. In a desktop application, that would be the UI. In many cases you want that - this allows you to update the UI with the response without any kind of marshalling. You can just write

var response = await client.GetAsync("www.someaddress.yo");
string content = await response.Content.ReadAsStringAsync();
textBox1.Text=content;

In other cases you may not want that, eg a library writer doesn't want the library to affect the client application. That's where ConfigureAwait(false) comes in :

var response = await client.GetAsync("www.someaddress.yo").ConfigureAwait(false);
string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

await with lambda expressions only

You can use Task.Run and await the returned task. You just need to mark this method with the async keyword and have it return a Task:

async Task FooAsync()
{
Customer cust = null;
await Task.Run(() =>
{
using (DataContext context = new DataContext())
{
cust = context.Customers.Single(c => c.Id == 1);
}
});
}

Now, if this is the top level UI event handler it can't return a Task and you need to use async void. This is only appropriate in UI event handlers.

If you can't make you method async and you still want to keep the operation asynchronous you can register the UI thread operations as a continuation with ContinueWith and TaskScheduler.FromCurrentSynchronizationContext to make sure the continuation runs on the UI thread:

void Foo()
{
Customer cust = null;
var task = Task.Run(() =>
{
using (DataContext context = new DataContext())
{
cust = context.Customers.Single(c => c.Id == 1);
}
});

task.ContinueWith(antecedent =>
{
// ...continue code in UI thread...
}, TaskScheduler.FromCurrentSynchronizationContext());
}

Async / await with task.run vs task.run and continuewith in .NET 4.0

It depends on what you're doing with ContinueWith. But yes, often you can use await to achieve the same effects you'd previously have achieved using ContinueWith. What you can't do is things like "continue with this code only on failure" - you just use normal exception handling for that. As AlexH says, there will be further differences in terms of the overall behaviour of your async method - but in most cases I'd say that's desirable. Basically, the asynchrony of the code flows, so async methods tend to call more async methods, etc.

I suggest you read up on what async/await is about (there are loads of resources out there - I'd recommend the "Consuming the Task-based Asynchronous Pattern" page on MSDN as one starting point.

C# ContinueWith / Return

That's the nature of async. Because your call is async, it will be executed later and the code below just continue executing.

Try adding an await and just remove the ContinueWith if this is the root of the call, usually it's an event handler:

private async Task<DTOVObs> TransFormAaretsGang(DTOAaretsGang aaretsGang)
{
var dtovObs = new DTOVObs();
DTOVArt Result = await artservice.GetVArtFormArt(new DTOArt() {ART_ID = aaretsGang.ART_ID});
dtovObs.Familie_id = Result.Familie_id;
dtovObs.Gruppe_id = Result.Gruppe_id;
dtovObs.ART_ID = Result.ART_ID;
if (aaretsGang.Dato != null)
dtovObs.Aarstal = aaretsGang.Dato.Value.Year;
return dtovObs;
}

If you still want to return an asynch Task so that any caller that calls this method can await the result, try:

private async Task<DTOVObs> TransFormAaretsGang(DTOAaretsGang aaretsGang)
{
using (var artservice = new ArtService())
{
return artservice.GetVArtFormArt(new DTOArt() {ART_ID = aaretsGang.ART_ID}).ContinueWith(t =>
{
dtovObs.Familie_id = t.Result.Familie_id;
dtovObs.Gruppe_id = t.Result.Gruppe_id;
dtovObs.ART_ID = t.Result.ART_ID;
if (aaretsGang.Dato != null) dtovObs.Aarstal = aaretsGang.Dato.Value.Year;
return dtovObs;
});
}
}

Async lambda to ExpressionFuncTask

C# can only convert lambda expression to Expression tree only if code can be represented by Expression Tree, if you notice, there is no equivalent of "async" keyword in Expressions in System.Linq.Expressions

So not only async, but anything in C# that has no equivalent expression in provided Expressions, C# can't convert it to Expression Tree.

Other examples are

  1. lock
  2. unsafe
  3. using
  4. yield
  5. await

Sequential await VS Continuation await

You'd want to use await if you possibly can.

ContinueWith has a number of issues. I describe this briefly in my blog post on why ContinueWith is dangerous, which builds on my earlier blog post on why StartNew is dangerous (they share many of the same issues).

In particular:

  • ContinueWith does not have a good default TaskScheduler. The default task scheduler is TaskScheduler.Current (not TaskScheduler.Default), and resuming on the current SynchronizationContext must be done by hand if desired.
  • ContinueWith has non-ideal default options. For asynchronous code, you would want DenyChildAttach and ExecuteSynchronously, at least.
  • ContinueWith does not understand asynchronous continuations, which generally requires an additional Unwrap call to work around this limitation.
  • ContinueWith treats its CancellationToken argument in a surprising way. More on that in this blog post of mine.

These arguments are all summarized on my blog post on Task continuations. await does not have any of these drawbacks.



Related Topics



Leave a reply



Submit