Замена методов, использующих backgroundworker для async/tpl (.NET 4.0) - программирование
Подтвердить что ты не робот

Замена методов, использующих 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, но ничего интересного и простого в использовании. Может ли кто-нибудь дать мне немного света?

4b9b3361

Ответ 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, хотите ли вы вызывать метод синхронно или асинхронно.