Я нашел решение, которое иногда работает адекватно.
Отдельная задача
var synchronizationContext = SynchronizationContext.Current;
var task = Task.Factory.StartNew(...);
task.ContinueWith(task =>
synchronizationContext.Post(state => {
if (!task.IsCanceled)
task.Wait();
}, null));
Это запланирует вызов на task.Wait()
в потоке пользовательского интерфейса.Поскольку я не выполняю Wait
, пока не узнаю, что задача уже выполнена, она фактически не будет блокироваться;он просто проверит, было ли исключение, и если да, то выдаст.Поскольку обратный вызов SynchronizationContext.Post
выполняется прямо из цикла сообщений (вне контекста Task
), TPL не остановит исключение и может распространяться как обычно, как если бы это было необработанное исключение вобработчик нажатия кнопки.
Еще одна проблема - я не хочу звонить WaitAll
, если задача была отменена.Если вы ожидаете отмененную задачу, TPL выдает TaskCanceledException
, которую нет смысла перебрасывать.
Несколько задач
В моем реальном коде у меня есть несколько задач -начальная задача и несколько продолжений.Если какой-либо из них (потенциально более одного) получает исключение, я хочу передать AggregateException
обратно в поток пользовательского интерфейса.Вот как с этим справиться:
var synchronizationContext = SynchronizationContext.Current;
var firstTask = Task.Factory.StartNew(...);
var secondTask = firstTask.ContinueWith(...);
var thirdTask = secondTask.ContinueWith(...);
Task.Factory.ContinueWhenAll(
new[] { firstTask, secondTask, thirdTask },
tasks => synchronizationContext.Post(state =>
Task.WaitAll(tasks.Where(task => !task.IsCanceled).ToArray()), null));
Та же история: как только все задачи будут выполнены, вызовите WaitAll
вне контекста Task
.Он не будет блокироваться, поскольку задачи уже выполнены;это просто простой способ бросить AggregateException
, если какая-либо из задач завершилась неудачей.
Сначала я переживал, что если в одной из задач продолжения используется что-то вроде TaskContinuationOptions.OnlyOnRanToCompletion
, ипервая задача завершилась ошибкой, затем вызов WaitAll
может зависнуть (поскольку задача продолжения никогда не будет запущена, и я беспокоился, что WaitAll
заблокирует ожидание ее запуска).Но оказывается, что разработчики TPL были умнее этого - если задача продолжения не будет запущена из-за флагов OnlyOn
или NotOn
, эта задача продолжения перейдет в состояние Canceled
, поэтому она не будет блокироватьсяWaitAll
.
Редактировать
Когда я использую версию с несколькими задачами, вызов WaitAll
выдает AggregateException
, но AggregateException
не доходит дообработчик ThreadException
: вместо этого ThreadException
передается только одно его внутренних исключений.Поэтому, если несколько задач выдавали исключения, только одна из них достигает обработчика потока-исключения.Я не понимаю, почему это так, но я пытаюсь понять это.