Подтвердить что ты не робот

Как я могу получить TaskScheduler для диспетчера?

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

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

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

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

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

Ответ 1

Шаг 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 (() => { ... });

Ответ 2

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

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

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

Ответ 3

Посмотрите TaskScheduler.FromCurrentSynchronizationContext. Рамка "Задачи" обеспечивает очень гибкий способ настройки выполнения связанных с вычислением операций, даже если существует конкретная модель потоков, навязанная приложением.

EDIT:

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

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

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

Ответ 4

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

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

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

var ts = Application.Current.Dispatcher.Invoke<TaskScheduler>(() => TaskScheduler.FromCurrentSynchronizationContext());

Ответ 5

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

    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
    }