Как я могу получить 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;
}
Ответы
Ответ 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
}