Является ли BackgroundWorker единственным способом сохранить приложение WCF/WPF отзывчивым?

клиент/сервер настольного приложения с помощью C#, WCF, WPF. Поскольку практически каждое действие потребует поездки на сервер (list/create/save/delete/etc), каждое действие может заморозить весь пользовательский интерфейс. Вот пример наивной реализации с вызовом service.GetAll() что может занять "долгое" время (более нескольких сотен миллисекунд):

private void btnRefresh_Click(object sender, RoutedEventArgs e)
{
    vm.Users.Clear();
    foreach (var user in service.GetAllUsers())
        vm.Users.Add(user);
}

(в сторону: я хотел бы знать, почему список и AddRange и ObservableCollection нет.)

BackgroundWorker на помощь:

private void btnRefresh_Click(object sender, RoutedEventArgs e)
{
    var worker = new BackgroundWorker();

    worker.DoWork += (s, e) =>
    {
        Dispatcher.BeginInvoke((Action)delegate() { btnRefresh.IsEnabled = false; });
        e.Result = service.GetAllUsers();
    };

    worker.RunWorkerCompleted += (s, e) =>
    {
        vm.Users.Clear();
        foreach (var user in (List<UserDto>)e.Result)
            vm.Users.Add(user);
        Dispatcher.BeginInvoke((Action)delegate() { btnRefresh.IsEnabled = true; });
    };

    worker.RunWorkerAsync();
}

(в сторону: код выше был упрощен,но в этом суть.)

код, используя BackgroundWorker работает так, как я хочу. Приложение остается отзывчивым во все времена, и кнопка отключена на время вызова. Однако это означает добавление 15 строк к каждому возможному действию Пользователя.

говорят, что это не так.

8 ответов


нет, BackgroundWorker - это не единственный способ, но это один из способов. Любой другой способ будет allso включать некоторую форму асинхронной конструкции с необходимостью использовать Dispatch.BeginInvoke для обновления пользовательского интерфейса. Вы можете, например, использовать ThreadPool:

ThreadPool.QueueUserWorkItem(state => {
    Dispatcher.BeginInvoke((Action)delegate() { btnRefresh.IsEnabled = false; });
    foreach (var user in service.GetAllUsers())
        vm.Users.Add(user);
    Dispatcher.BeginInvoke((Action)delegate() { btnRefresh.IsEnabled = true; });

});

если это повторяющийся шаблон (кнопка вызовет некоторое действие, которое должно выполняться асинхронно, при этом кнопка отключена во время процесса), вы можете обернуть это в метод:

private void PerformAsync(Action action, Control triggeringControl)
{
    ThreadPool.QueueUserWorkItem(state => {
        Dispatcher.BeginInvoke((Action)delegate() { triggeringControl.IsEnabled = false; });
        action();
        Dispatcher.BeginInvoke((Action)delegate() { triggeringControl.IsEnabled = true; });     
    });
}

...и звоните это:

PerformAsync(() => 
{
    foreach (var user in service.GetAllUsers())
        vm.Users.Add(user);
}, btnRefresh);

как вариант с помощью ThreadPool, вы также должны, возможно, заглянуть в Параллельных Задач Библиотека.

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

Примечание: это просто быстрые идеи. Код не был протестирован, поэтому он может содержать ошибки. Это больше следует рассматривать как дискуссионный материал, чем готовые решения.


WCF предоставляет возможность выполнять все вызовы служб асинхронно. При создании ссылки на службу в проекте диалоговое окно добавление ссылки на службу имеет значение "дополнительно"..." кнопка. Нажав на это, вы увидите опцию "генерировать асинхронные операции". Если вы установите этот флажок, то каждая операция будет создаваться синхронно и асинхронно.

например, если у меня есть операция " DoSomething ()", то после установки этого флажка я буду получите код, сгенерированный для вызова DoSomething () и DoSomethingAsync ().

вы также получите службу.Dosomethingcompleted событие, которое можно использовать для определения обработчика обратного вызова при возврате вызова службы.

Это метод, который мы использовали для выполнения вызовов службы без блокировки пользовательского интерфейса.

вот довольно сложный пример, предоставленный Microsoft о том, как это сделать:http://msdn.microsoft.com/en-us/library/ms730059.aspx


это не единственный способ. Я рекомендую Task (или одна из абстракций более высокого уровня для Task, например Parallel или PLINQ).

у меня есть обзор различных подходов к асинхронным фоновым операциям мой блог.

текущее положение вещей требует некоторого шаблонного кода, независимо от того, какой подход вы выберете. The асинхронный CTP показывает, куда все идет-к гораздо более чистому синтаксису для асинхронного оперативный. (Обратите внимание, что - на момент написания-асинхронный CTP несовместим с VS SP1).


Ну, BackgroundWorker-это не единственный вариант, который у вас есть, но для выполнения того, что вы хотите, вам все равно нужно использовать несколько потоков или асинхронных операций, чтобы не блокировать, пока вы ждете завершения длительных операций.

и, поскольку WPF требует, чтобы весь код, обращающийся к пользовательскому интерфейсу, выполнялся в одном потоке, вам нужно сделать некоторое переключение контекста при вызове или доступе к данным или коду в потоке пользовательского интерфейса. Способ обеспечения запуска вызова в потоке UI в WPF должен использовать Диспечер класс.

еще один простой способ сохранить отзывчивый пользовательский интерфейс-это очередь рабочего элемента в потоке в пуле потоков, которая выполняется с помощью класса ThreadPool.

// assuming the the following code resides in a WPF control
//  hence "this" is a reference to a WPF control which has a Dispatcher
System.Threading.ThreadPool.QueueUserWorkItem((WaitCallback)delegate{
    // put long-running code here

    // get the result

    // now use the Dispatcher to invoke code back on the UI thread
    this.Dispatcher.Invoke(DispatcherPriority.Normal,
             (Action)delegate(){
                  // this code would be scheduled to run on the UI
             });
});

Как всегда, есть несколько способов освежевать кошку, но имейте в виду, что каждый метод имеет преимущества и недостатки. Например, описанный выше метод может быть полезен, потому что у него не так много накладных расходов кода, но он может быть не самым эффективным во многих случаях может.

другие опции доступны, включая использование BeginXXX-EndXXX методы классов, которые вы используете, если они предоставляют какие-либо (например,sqlcommand, который класс BeginExecuteReader EndExecuteReader). Или, используя методы XXXAsync если классы есть. Например, Система.Сеть.Соке.Сокет класс имеет ReceiveAsync и SendAsync.


нет это не единственный вариант. Это вопрос больше о том, как вы разрабатываете свое приложение.

вы можете взглянуть на Windows Composite Applicaiton Framework (Prism), который предоставляет такие функции, как EventAggregator, которые могут помочь вам публиковать широкие события приложения и подписываться на него в нескольких местах в вашем приложении и предпринимать действия на основе этого.

также как переживал от того, что слишком много строк кода, Вы можете чтобы слой архитектуру приложения таким образом, что можно переработать и повторно использовать как можно больше кода. Таким образом, эти фоновые работники обрабатывают все ваши ответы службы в одном слое, в то время как вы можете оставить свой слой UI отдельно от него.


нет, это не единственный способ, но он один из самых простых (по крайней мере, по сравнению с настройкой собственного потока или передачей задачи в поток пула потоков и организацией события по завершении).


возможно, вы сможете немного упростить, написав статический метод где-нибудь, который принимает два параметра, функции обратного вызова и обрабатывает остальные для вас, таким образом, вам не придется писать все ту же котельную плиту каждый раз, когда вам нужно сделать асинхронный вызов.


нет, certaily не.

вы можете создать raw нить и выполнить время, принимая код в нем, а затем отправить код в поток пользовательского интерфейса для доступа / обновления любых элементов управления пользовательского интерфейса.Больше информации о Disptacher здесь.

смотрите этой большой информации о потоках в C#.