How can I get a TaskScheduler for a Dispatcher? How can I get a TaskScheduler for a Dispatcher? wpf wpf

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:

  1. 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.
  2. Use Dispatcher.BeginInvoke to run some code on the dispatcher thread, and in that code, call TaskScheduler.FromCurrentSynchronizationContext. (In other words, if you can't arrange for 1. to happen naturally, force it to happen.)
  3. 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...