Замена методов, использующих backgroundworker для async/tpl (.NET 4.0)
У меня много вопросов. С тех пор я видел .NET 4.5, я был очень впечатлен. К сожалению, все мои проекты -.NET 4.0, и я не думаю о миграции. Поэтому я хотел бы упростить свой код.
В настоящее время большая часть моего кода, который обычно занимает достаточно времени, чтобы заморозить экран, я делаю следующее:
BackgroundWorker bd = new BackgroundWorker();
bd.DoWork += (a, r) =>
{
r.Result = ProcessMethod(r.Argument);
};
bd.RunWorkerCompleted += (a, r) =>
{
UpdateView(r.Result);
};
bd.RunWorkerAsync(args);
Честно говоря, я устал от этого. И это становится большой проблемой при наличии логического сложного взаимодействия с пользователем.
Интересно, как упростить эту логику? (Помните, что я с Net 4.0). Я заметил несколько вещей от Google, но не нашел ничего легкого в реализации и подходящего для моих нужд.
Я подумал об этом решении ниже:
var foo = args as Foo;
var result = AsyncHelper.CustomInvoke<Foo>(ProcessMethod, foo);
UpdateView(result);
public static class AsyncHelper
{
public static T CustomInvoke<T>(Func<T, T> func, T param) where T : class
{
T result = null;
DispatcherFrame frame = new DispatcherFrame();
Task.Factory.StartNew(() =>
{
result = func(param);
frame.Continue = false;
});
Dispatcher.PushFrame(frame);
return result;
}
}
Я не уверен, что это воздействие на манипулирование рамкой диспетчера.
Но я знаю, что он будет работать очень хорошо, например, я мог бы использовать его во всех событиях элементов управления, не беспокоясь о замораживании экрана.
Мои знания об общих типах, ковариации, контравариантности ограничены, возможно, этот код можно улучшить.
Я думал о других вещах, используя Task.Factory.StartNew
и Dispatcher.Invoke
, но ничего интересного и простого в использовании. Может ли кто-нибудь дать мне немного света?
Ответы
Ответ 1
Вы должны просто использовать параллельную библиотеку задач (TPL). Ключ указывает TaskScheduler
для текущего SynchronizationContext
для любых продолжений, в которых вы обновляете пользовательский интерфейс. Например:
Task.Factory.StartNew(() =>
{
return ProcessMethod(yourArgument);
})
.ContinueWith(antecedent =>
{
UpdateView(antecedent.Result);
},
TaskScheduler.FromCurrentSynchronizationContext());
Помимо обработки некоторых исключений при доступе к свойству антецедента Result
, все это тоже есть. Используя FromCurrentSynchronizationContext()
, для выполнения продолжения будет использоваться окружающий SynchronizationContext, который поступает из WPF (т.е. DispatcherSynchronizationContext). Это то же самое, что и вызов Dispatcher.[Begin]Invoke
, но вы полностью абстрагированы от него.
Если вы хотите получить "очиститель", если вы контролируете ProcessMethod, я бы действительно переписал это, чтобы вернуть Task
и позволить ему владеть тем, как он закручивается (можно использовать StartNew
внутри). Таким образом, вы абстрагируете вызывающего из решений асинхронного исполнения, которые ProcessMethod может захотеть сделать самостоятельно, и вместо этого им нужно только беспокоиться о цепочке продолжения, чтобы дождаться результата.
ОБНОВЛЕНИЕ 5/22/2013
Следует отметить, что с появлением .NET 4.5 и поддержки асинхронного языка в С# этот предписанный метод устарел, и вы можете просто полагаться на эти функции для выполнения конкретной задачи с помощью await Task.Run
, а затем выполнение после этого будет снова выполняются в Диспетчерской нити автоматически. Так что-то вроде этого:
MyResultType processingResult = await Task.Run(() =>
{
return ProcessMethod(yourArgument);
});
UpdateView(processingResult);
Ответ 2
Как насчет инкапсуляции кода, который всегда является одним и тем же компонентом многократного использования? Вы можете создать Freezable, который реализует ICommand, предоставляет свойство Type DoWorkEventHandler и свойство Result. В ICommand.Executed он создаст BackgroundWorker и подключит делегатов для DoWork и Completed, используя значение DoWorkEventHandler как обработчик событий, и обработает Completed таким образом, чтобы он установил свой собственный свойство Result в результат, возвращаемый в событие.
Вы сконфигурировали компонент в XAML, используя конвертер для привязки свойства DoWorkEventHandler к методу в ViewModel (предположим, у вас его есть) и привяжите свой вид к компоненту Result, поэтому он обновляется автоматически, когда Result делает уведомление об изменении.
Преимущества этого решения заключаются в следующем: он многоразовый, и он работает только с XAML, поэтому больше нет кода клейма в ViewModel для обработки BackgroundWorkers. Если вам не нужен фоновый процесс для отчета о прогрессе, он даже может не знать, что он работает в фоновом потоке, поэтому вы можете принять решение в XAML, хотите ли вы вызывать метод синхронно или асинхронно.
Ответ 3
Прошло несколько месяцев, но может ли это помочь?
Использование async/await без .NET Framework 4.5