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
- lock
- unsafe
- using
- yield
- 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 defaultTaskScheduler
. The default task scheduler isTaskScheduler.Current
(notTaskScheduler.Default
), and resuming on the currentSynchronizationContext
must be done by hand if desired.ContinueWith
has non-ideal default options. For asynchronous code, you would wantDenyChildAttach
andExecuteSynchronously
, at least.ContinueWith
does not understand asynchronous continuations, which generally requires an additionalUnwrap
call to work around this limitation.ContinueWith
treats itsCancellationToken
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
C# Sending Mails with Images Inline Using Smtpclient
Create Bitmap from a Byte Array of Pixel Data
How to Find All Implementations of an Interface
Get Os Version/Friendly Name in C#
Validation Error Style in Wpf, Similar to Silverlight
Posting JSON Data to ASP.NET MVC
How to Accept an Array as an ASP.NET MVC Controller Action Parameter
Why Can't Yield Return Appear Inside a Try Block with a Catch
Get Property Name and Type Using Lambda Expression
How to Copy Part of an Array to Another Array in C#
How to Bring My Application Window to the Front
How to Seed in Entity Framework Core 2
Play and Wait for Animation/Animator to Finish Playing
How to Backup and Restore the System Clipboard in C#
Best Practice to Make a Multi Language Application in C#/Winforms