-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow an await to capture the current TaskScheduler instead of the current SynchronizationContext #47433
Comments
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label. |
Tagging subscribers to this area: @tarekgh Issue DetailsBackground and MotivationThe default behavior of the Proposed APIA new overload of the
This is an extension of the API proposed on the issue #22144. Usage ExamplesIt will be possible to build custom
A complete example can be found here. The correct measurement is ~2,500 msec (five times Another possibility would be to build asynchronous APIs that accept an async delegate together with a
The current RisksIn the above example, if the user neglects to configure all
|
CC @stephentoub |
When I made this suggestion I hadn't realized (initially) that it is possible to configure the public struct TaskSchedulerAwaitable : INotifyCompletion
{
private readonly ConfiguredTaskAwaitable.ConfiguredTaskAwaiter _taskAwaiter;
private readonly TaskScheduler _capturedScheduler;
public TaskSchedulerAwaitable(Task task)
{
_taskAwaiter = task.ConfigureAwait(false).GetAwaiter();
_capturedScheduler = TaskScheduler.Current;
}
public TaskSchedulerAwaitable GetAwaiter() => this;
public bool IsCompleted { get { return _taskAwaiter.IsCompleted; } }
public void GetResult() { _taskAwaiter.GetResult(); }
public void OnCompleted(Action continuation)
{
var self = this;
_taskAwaiter.OnCompleted(() =>
{
if (TaskScheduler.Current == self._capturedScheduler)
{
continuation();
}
else
{
Task.Factory.StartNew(continuation, default,
TaskCreationOptions.DenyChildAttach, self._capturedScheduler);
}
});
}
} Usage example: await new TaskSchedulerAwaitable(Task.Delay(1000)); // Continue on the current TaskScheduler |
Yup. Thanks. |
Background and Motivation
The default behavior of the
await
operator is to capture the currentSynchronizationContext
, if one exists, and otherwise capture the currentTaskScheduler
. This behavior can be configured with theConfigureAwait(false)
, which disables capturing both of these mechanisms. Currently it is not possible for a programmer to prioritize capturing the currentTaskScheduler
instead of the currentSynchronizationContext
, if they so wish for some reason. This results to theTaskScheduler
mechanism being severely handicapped in the presence of an ambientSynchronizationContext
. In this case it can schedule only the first part of an asynchronous method, until the firstawait
. All continuations after the firstawait
are presented with an ambientTaskScheduler.Current
namedThreadPoolTaskScheduler
. The programmatically configuredTaskScheduler
is ignored, and the programmer can't do really much about it.Proposed API
A new overload of the
ConfigureAwait
method that accept a newConfigureAwaitBehavior
enum, having anOnlyCaptureTaskScheduler
value:This is an extension of the API proposed on the issue #22144.
Usage Examples
It will be possible to build custom
TaskScheduler
s that are aware of the whole lifetime of an asynchronous method, whether or not an ambientSynchronizationContext
is present. As an example one could build aTaskScheduler
that measures the total duration that an asynchronous method is doing synchronous work or is blocked. In order for this to work, that method should have all itsawait
s configured withOnlyCaptureTaskScheduler
.A complete example can be found here. The correct measurement is ~2,500 msec (five times
Thread.Sleep(500)
). The currentawait
behavior gives an incorrect measurement of ~500 msec.Another possibility would be to build asynchronous APIs that accept an async delegate together with a
TaskScheduler
, and ensure that all invocations of the delegate will be on thatTaskScheduler
. See for example theRetry
method below:The current
await
behavior discourages async APIs with aTaskScheduler
parameter, because the only reliable way to respect the parameter is to start a new configured task for each invocation.Risks
In the above example, if the user neglects to configure all
await
s inside their ownaction
in the same way, theaction
will be invoked on the suppliedtaskScheduler
only partially. This may be against their expectations.The text was updated successfully, but these errors were encountered: