dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.08k stars 4.7k forks source link

Allow setting TaskScheduler.Current #29593

Open ReubenBond opened 5 years ago

ReubenBond commented 5 years ago

Orleans is one of the (seemingly few) frameworks which makes heavy use of custom TaskSchedulers. We use them to implement single-threaded execution for grains (~distributed objects). Each grain has its own ActivationTaskScheduler, so there may be 10^5 or 10^6 TaskScheduler instances in a process at any one time (on the high end).

ActivationTaskScheduler is a wrapper around WorkItemGroup, a collection of work items to execute serially. When a work item (eg, a Task) is scheduled, the WorkItemGroup itself is scheduled onto a dedicated thread pool for execution. This is very similar to IThreadPoolWorkItem, so there's room for us to support IThreadPoolWorkItem and also use the default thread pool in future. EDIT 2021: Those WorkItemGroup instances are now scheduled on the ThreadPool via IThreadPoolWorkItem.

Each grain sends/receives calls via RPC interfaces. We schedule those messages onto the grain's WorkItemGroup, but we have to wrap these work items in a Task so we can maintain single-threadedness in the case that the user's code doesn't complete synchronously or kicks off some background work (fire-and-forget style). For example, a JoinGame(id) call might contact some other grains and write to storage. This wrapping is not ideal.

If we could set & reset TaskScheduler.Current from within our WorkItemGroup then we could avoid this wrapping while still being able to have single-threadedness.

Is exposing a TaskScheduler.Current setter a possibility? Failing that, what other options should we consider?

I recognize potential pitfalls associated with broadly exposing a setter for TaskScheduler.Current. If that is a major concern then perhaps we could expose some scary-looking API like TaskScheduler.UnsafeSetCurrent(TaskScheduler) or some alternative means to control the ambient scheduler which doesn't require allocating a task.

stephentoub commented 1 year ago

In theory I'm not against the idea of a TaskScheduler.SetCurrent. In practice I'm not sure how it would work, at least not without an overhaul of this area of tasks. Today TaskScheduler.get_Current is effectively just:

Task t = Task.CurrentTask; // gets whatever innermost Task is currently executing on this thread
return t?.Scheduler ?? TaskScheduler.Default;

so there isn't really a notion of a stored current scheduler like you'd need to implement SetCurrent. Someone would need to prototype this in corelib to get a sense for the implications... I don't know if it'd end up being trivial or a rewrite or somewhere in between.