-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
Description
There are 6 overloads for Task.WhenAny
:
WhenAny(IEnumerable<Task>)
WhenAny(Task[])
WhenAny(Task, Task)
WhenAny<TResult>(IEnumerable<Task<TResult>>)
WhenAny<TResult>(Task<TResult>[])
WhenAny<TResult>(Task<TResult>, Task<TResult>)
It appears that 1, 2, 3, and 6 properly use the current task scheduler only and never the default scheduler, but that 4 and 5 use ContinueWith
on the default scheduler to schedule the casting. To make this more confusing, if you call 5 with an array of 2 it uses a different scheduler than calling 5 with an array of 3.
For my use case, I have to prevent things from getting on the default scheduler for determinism (and write an analyzer for this). This is easy to know when it's ContinueWith
or Run
or Start
or whatever which all have predictable scheduler choice (be it default or current). But for WhenAny
it's not easily predictable.
See
runtime/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs
Lines 6561 to 6562 in 90c5f05
return intermediate.ContinueWith(Task<TResult>.TaskWhenAnyCast.Value, default, | |
TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default); |
runtime/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs
Lines 6612 to 6613 in 90c5f05
return intermediate.ContinueWith(Task<TResult>.TaskWhenAnyCast.Value, default, | |
TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default); |
Reproduction Steps
With a custom scheduler as current, call await Task.WhenAny(new[] { Task.FromResult("a"), Task.FromResult("b") })
and confirm the default scheduler is not used for continuation but await Task.WhenAny(new[] { Task.FromResult("a"), Task.FromResult("b"), Task.FromResult("c") })
does.
I caught this via my TplEventSource
listener.
Expected behavior
Any .NET task (or task factory) calls should clearly use one scheduler or another, not mix them. And the documentation should say as much. Maybe the continue-with can use the first task's scheduler?
(or I am wrong, maybe this is the expected behavior, and I should never expect to know that default scheduler will not be used internally)
Actual behavior
Depending on which WhenAny
call you make, you may end up running something on the default scheduler.
Regression?
No response
Known Workarounds
I could stop using event source listeners to catch people putting tasks on the wrong scheduler, but it leaves users open to accidents.
Configuration
No response
Other information
No response