Как я могу получить TaskScheduler для Диспетчера? - PullRequest
17 голосов
/ 16 июня 2011

У меня есть приложение с несколькими Dispatcher s (так называемые потоки графического интерфейса, так называемые насосы сообщений), чтобы гарантировать, что медленная, неотзывчивая часть графического интерфейса пользователя работает без чрезмерного влияния на остальную часть приложения.Я также много использую Task.

В настоящее время у меня есть код, который условно запускает Action на TaskScheduler или Dispatcher, а затем возвращает Task либо напрямую, либо вручнуюсоздание с использованием TaskCompletionSource.Тем не менее, этот разделенный дизайн личности делает гораздо более сложным, чем я хотел бы иметь дело с отменой, исключениями и т. Д.Я хочу использовать Task s везде и DispatcherOperation s нигде.Для этого мне нужно запланировать задачи для диспетчеров, но как?

Как получить TaskScheduler для любого заданного Dispatcher?

Редактировать: ПослеПри обсуждении ниже я остановился на следующей реализации:

public static Task<TaskScheduler> GetScheduler(Dispatcher d) {
    var schedulerResult = new TaskCompletionSource<TaskScheduler>();
    d.BeginInvoke(() => 
        schedulerResult.SetResult(
            TaskScheduler.FromCurrentSynchronizationContext()));
    return schedulerResult.Task;
}

Ответы [ 5 ]

14 голосов
/ 02 мая 2012

Шаг 1. Создание метода расширения:

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;
}

Шаг 2. Использование метода расширения:

Старый синтаксис :

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 await
var taskFactory = taskFactoryAsync.Result;
var task = taskFactory.StartNew (() => { ... });

Новый синтаксис :

var taskScheduler = await Dispatcher.CurrentDispatcher.ToTaskSchedulerAsync ();
var taskFactory = new TaskFactory (taskScheduler);
var task = taskFactory.StartNew (() => { ... });
9 голосов
/ 16 июня 2011

К сожалению, нет встроенного способа сделать это. Не существует встроенного класса, предназначенного для переноса Dispatcher в TaskScheduler - наиболее близким у нас является тот, который упаковывает SynchronizationContext. И единственный публичный API для построения TaskScheduler из SynchronizationContext - это тот, который упоминает Пол Михалик: TaskScheduler.FromCurrentSynchronizationContext - и, как вы заметили, он работает, только если вы уже находитесь в соответствующем контексте синхронизации (то есть в соответствующая ветка диспетчера).

Итак, у вас есть три варианта:

  1. Создайте свой код так, чтобы класс, которому требуются планировщики для соответствующих диспетчеров, мог в какой-то момент иметь возможность работать в потоках этих диспетчеров, чтобы вы могли использовать TaskScheduler.FromCurrentSynchronizationContext по назначению.
  2. Используйте Dispatcher.BeginInvoke для запуска некоторого кода в потоке диспетчера, и в этом коде вызовите TaskScheduler.FromCurrentSynchronizationContext. (Другими словами, если вы не можете 1. организовать естественным образом, заставьте это случиться.)
  3. Напишите свой собственный планировщик задач.
4 голосов
/ 16 июня 2011

Посмотрите на TaskScheduler.FromCurrentSynchronizationContext. Инфраструктура задач обеспечивает очень гибкий способ настройки выполнения операций с привязкой к вычислениям, даже если приложение применяет конкретную модель потоков.

EDIT:

Хм, из того, что вы опубликовали, трудно получить более четкое представление. Я понимаю, что вы используете приложение с несколькими представлениями с отдельными диспетчерами для каждого представления, верно? Так как вся диспетчеризация сводится к получению SynchronizationContext и Post -ing к нему, вы можете получить правильные TaskScheduler (тот, который с правильными SynchronizationContext) в некоторый момент, когда ваши взгляды получили один. Простой способ сделать это - получить TaskScheduler во время конфигурирования тактов:

 // 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();

Не уверен, поможет ли это, если вы широко используете Задачи, вы, вероятно, уже это знаете ...

3 голосов
/ 13 ноября 2014

Вы могли бы написать целую функцию в одну строку:

public static Task<TaskScheduler> ToTaskSchedulerAsync(this Dispatcher dispatcher,
                           DispatcherPriority priority = DispatcherPriority.Normal)
{
    return dispatcher.InvokeAsync<TaskScheduler>(() =>
         TaskScheduler.FromCurrentSynchronizationContext(), priority).Task;
}

, и те, кто доволен потоком пользовательского интерфейса по умолчанию, могут найти следующее достаточно, чтобы получить:

0 голосов
/ 23 августа 2011

Вот так я всегда преобразую вызов асинхронной функции в вызов функции синхронизации (слава кому-то в Интернете):

    public static class ThreadingUtils 
    {
         public static TaskScheduler GetScheduler(Dispatcher dispatcher)
         {
             using (var waiter = new ManualResetEvent(false))
             {
                 TaskScheduler scheduler = null;
                 dispatcher.BeginInvoke(new Action(() =>
                 {
                     scheduler = 
                         TaskScheduler.FromCurrentSynchronizationContext();
                     waiter.Set();
                 }));
                 waiter.WaitOne();
                 return scheduler;
             }
         }
    }

Вариация:

    if (!waiter.WaitOne(2000))
    { 
        //Timeout connecting to server, log and exit
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...