How can I get a TaskScheduler for a Dispatcher?
Step 1: Create an extension method:
public static Task<TaskScheduler> ToTaskSchedulerAsync ( this Dispatcher dispatcher, DispatcherPriority priority = DispatcherPriority.Normal) { var taskCompletionSource = new TaskCompletionSource<TaskScheduler> (); var invocation = dispatcher.BeginInvoke (new Action (() => taskCompletionSource.SetResult ( TaskScheduler.FromCurrentSynchronizationContext ())), priority); invocation.Aborted += (s, e) => taskCompletionSource.SetCanceled (); return taskCompletionSource.Task;}
Step 2: Use the extension method:
Old syntax:
var taskSchedulerAsync = Dispatcher.CurrentDispatcher.ToTaskSchedulerAsync ();var taskFactoryAsync = taskSchedulerAsync.ContinueWith<TaskFactory> (_ => new TaskFactory (taskSchedulerAsync.Result), TaskContinuationOptions.OnlyOnRanToCompletion);// this is the only blocking statement, not needed once we have awaitvar taskFactory = taskFactoryAsync.Result;var task = taskFactory.StartNew (() => { ... });
New syntax:
var taskScheduler = await Dispatcher.CurrentDispatcher.ToTaskSchedulerAsync ();var taskFactory = new TaskFactory (taskScheduler);var task = taskFactory.StartNew (() => { ... });
Unfortunately, there's no built-in way to do this. There is no built-in class dedicated to wrapping a Dispatcher
in a TaskScheduler
- the closest thing we have is the one that wraps a SynchronizationContext
. And the only public API for building a TaskScheduler
from a SynchronizationContext
is the one Paul Michalik mentions: TaskScheduler.FromCurrentSynchronizationContext
- and as you observe, that only works if you're already in the relevent synchronization context (i.e., on the relevant dispatcher's thread).
So you have three choices:
- Arrange your code so that the class that needs schedulers for the relevant dispatchers will get a chance to run on those dispatchers' threads at some point, so that you can use
TaskScheduler.FromCurrentSynchronizationContext
as intended. - Use
Dispatcher.BeginInvoke
to run some code on the dispatcher thread, and in that code, callTaskScheduler.FromCurrentSynchronizationContext
. (In other words, if you can't arrange for 1. to happen naturally, force it to happen.) - Write your own task scheduler.
Have a look at TaskScheduler.FromCurrentSynchronizationContext
. The Task framework provides a very flexible way to configure the execution of compute bound operations even if there is a specific threading model imposed by the application.
EDIT:
Hm, it is hard to get more explicit from what you have posted. I understand that you´re running sort of multi-view application with separate dispatchers for each view, right? Since all the dispatching boils down to fetching a SynchronizationContext
and Post
-ing to it you can fetch the right TaskScheduler
(the one with the correct SynchronizationContext
) at some point where your view(s) got one. A simple way to do that would be to get a TaskScheduler during the configuration of the taks(s):
// somewhere on GUI thread you wish to invoke // a long running operation which returns an Int32 and posts // its result in a control accessible via this.Text (new Task<Int32>(DoSomeAsyncOperationReturningInt32) .ContinueWith(tTask => this.Text = tTask.Result.ToString(), TaskScheduler.FromCurrentSynchronizationContext)).Start();
Not sure if this helps, if your are using Tasks extensively you´ll probably already know that...