What's the equivalent of using:
await task.ConfigureAwait(false);
when using continuations like so (without using the C# compiler's async/await
transformations)?
var taskOfString = ScheduleWorkOnThreadPoolAsync();
// I'd like this continuation to not have to
// "flow" the synchronization context but to simply
// execute wherever it can, i.e. I'd like to tell is
// ConfigureAwait(false) for its previous task.
// How do I do that?
taskOfString.ContinueWith(t => { });
public async Task<string> ScheduleWorkOnThreadPoolAsync()
{
return Task.Run(() => return "Foo" );
}
I am assuming that doing nothing, i.e. just leaving it as is is equivalent to calling ConfigureAwait(false)
, which is also what I see happening when I debug the code. It hops on whatever thread it can.
It is only when we want to specify a scheduler or synchronization context to run the continuation on that we need to pass in extra information to the overload that accepts a TaskScheduler
. Otherwise, it is defaulted to run without any regard to the execution context.
However, I still request a confirmation or correction if I am wrong.
when using continuations like so (without using the C# compiler's async/await transformations)?
You should almost always use async
/await
. They have far safer default behavior. ContinueWith
is a dangerous, low-level API.
I am assuming that doing nothing, i.e. just leaving it as is is equivalent to calling ConfigureAwait(false)... It is only when we want to specify a scheduler or synchronization context to run the continuation on that we need to pass in extra information to the overload that accepts a TaskScheduler. Otherwise, it is defaulted to run without any regard to the execution context.
No. This is incorrect, although simple tests won't reveal the problem.
As I describe in my blog post on why you shouldn't use ContinueWith
, the default TaskScheduler
for ContinueWith
is not TaskScheduler.Default
. It's TaskScheduler.Current
. Since this is confusing in every scenario, you should always pass a TaskScheduler
to ContinueWith
and StartNew
.
If you want await x.ConfigureAwait(false)
behavior, you would actually do:
var continuation = x.ContinueWith(callback, CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.DenyChildAttach,
TaskScheduler.Default);
The TaskContinuationOptions.ExecuteSynchronously
emulates the synchronous-if-possible behavior of await
. The TaskContinuationOptions.DenyChildAttach
prevents problems if the continuation task is attached to (tasks intended for asynchronous have surprising behavior when child tasks attach to them with AttachedToParent
). The TaskScheduler.Default
emulates the ConfigureAwait(false)
behavior of always executing on a thread pool context.
As a final note, you'll probably need to do something with continuation
- at least observe it for exceptions and handle them somehow.
At this point, it should be clear why I recommend await
. At worst, you'd just need to add a helper method to use await
instead of ContinueWith
- await
is much more maintainable, IMO.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With