Разве это не было .NET 4.0 TPL, из-за чего асинхронные шаблоны APM, EAP и BackgroundWorker устарели?

У меня есть 2 вида проектов приложений С# WPF:

  • на .NET 4.0, который я не могу перенести на .NET 4.5
  • на основе .NET 4.0, с которым я могу перейти на .NET 4.5

Все они должны запускать 2-10 длительных (дней) процессов, которые могут быть отменены и повторно запущены пользователями.

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

Я вижу (противоречащие) параллельные точки зрения о

асинхронные шаблоны:

"Асинхронный подход к асинхронному программированию предпочтительнее существующих подходов практически в каждом случае. В частности, этот подход лучше BackgroundWorker для операций с привязкой к IO, потому что код проще, и вам не нужно защищать от условий гонки. В сочетании с Task.Run асинхронное программирование лучше, чем BackgroundWorker для операций с привязкой к процессору, потому что асинхронное программирование разделяет координационные данные о выполнении вашего кода из работы что Task.Run передается в threadpool "

Я все еще сомневаюсь:

  • Являются ли эти шаблоны (в первую очередь, BGW) устаревшими в .NET 4.5?
  • Если они устарели в .NET 4.5, почему они не устарели в .NET 4.0?

    2A) Не понимаю ли я неправильно, что новые возможности .NET 4.5 все еще "легки" для реализации/воспроизводимости в .NET 4.0?

Ответ 1

Обычно я рекомендую Task и/или await при использовании .NET 4.5. Но Task и BGW имеют 2 совершенно разных сценария. Задача хороша для общих коротких асинхронных задач, которые могут быть привязаны к продолжению и ждут хороших задач, которые неявно перестраиваются обратно в поток пользовательского интерфейса. BGW подходит для одной длительной операции, которая не должна влиять на отзывчивость вашего пользовательского интерфейса. Вы можете перетащить BGW на поверхность дизайна и дважды щелкнуть, чтобы создать обработчики событий. Вам не нужно иметь дело с LongRunning или ConfigureAwait, если вы не хотите маршалировать другой поток. Многие находят прогресс BGW легче, чем IProgress<T>.

Вот несколько примеров использования обоих сценариев "продолжительной операции":

Поскольку в конкретном вопросе упоминается .NET 4.0, следующий простой код, который использует Task для выполнения длительной операции при обеспечении прогресса в пользовательском интерфейсе:

startButton.Enabled = false;
var task = Task.Factory.
                StartNew(() =>
                    {
                        foreach (var x in Enumerable.Range(1, 10))
                        {
                            var progress = x*10;
                            Thread.Sleep(500); // fake work
                            BeginInvoke((Action) delegate { 
                               progressBar1.Value = progress; 
                            });
                        }
                    }, TaskCreationOptions.LongRunning)
                .ContinueWith(t =>
                    {
                        startButton.Enabled = true;
                        progressBar1.Value = 0;
                    });

Аналогичный код с BackgroundWorker может быть:

startButton.Enabled = false;
BackgroundWorker bgw = new BackgroundWorker { WorkerReportsProgress = true };
bgw.ProgressChanged += (sender, args) => 
    { progressBar1.Value = args.ProgressPercentage; };
bgw.RunWorkerCompleted += (sender, args) =>
{
    startButton.Enabled = true;
    progressBar1.Value = 0;
};
bgw.DoWork += (sender, args) =>
{
    foreach (var x in Enumerable.Range(1, 10))
    {
        Thread.Sleep(500);
        ((BackgroundWorker)sender).ReportProgress(x * 10);
    }
};
bgw.RunWorkerAsync();

Теперь, если вы использовали .NET 4.5, вы можете использовать Progress<T> вместо вызова BeginInvoke с Task. И поскольку в 4.5, использование await, скорее всего, будет более читаемым:

startButton.Enabled = false;
var pr = new Progress<int>();
pr.ProgressChanged += (o, i) => progressBar1.Value = i;
await Task.Factory.
            StartNew(() =>
                        {
                            foreach (var x in Enumerable.Range(1, 10))
                            {
                                Thread.Sleep(500); // fake work
                                ((IProgress<int>) pr).Report(x*10);
                            }
                        }, TaskCreationOptions.LongRunning);
startButton.Enabled = true;
progressBar1.Value = 0;

Использование Progress<T> означает, что код не связан с определенным интерфейсом пользовательского интерфейса (т.е. вызов BeginInvoke) во многом таким же образом, что BackgroundWorker облегчает развязку из определенной структуры пользовательского интерфейса. Если вам все равно, то вам не нужно вводить дополнительную сложность использования Progress<T>

Что касается LongRunning, как говорит Стивен Туб: "Обычно вы используете LongRunning только в том случае, если вы обнаружили, что тестирование производительности не использует его, что приводит к длительным задержкам в обработке другой работы", поэтому, если вы считаете нужным чтобы использовать его, тогда вы его используете - там добавлен анализ или просто "сложность" всегда добавления параметра LongRunning. Не использование LongRunning означает, что поток потока потоков, используемый для продолжительной работы, не будет использоваться для других, более сложных задач и может заставить пул потоков задерживать запуск одной из этих переходных задач, пока он запускает другой поток (по крайней мере, второй).

В структуре нет атрибутов, которые специально говорят, что BGW (или EAP или APM) устарели. Итак, вам решать, где и когда какая-либо из этих вещей "устарела". В BGW, в частности, всегда был очень специфический сценарий использования, который по-прежнему применяется к нему. У вас достаточно достойные альтернативы в .NET 4.0 и 4.5; но я действительно не думаю, что BGW "устарела".

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

Ответ 2

Я рассматриваю эти шаблоны (в частности, APM, EAP и BGW), устаревшие в .NET 4.5. Комбинация async с Task.Run превосходит BGW во всех отношениях. Фактически, я только что начал серию в своем блоге, где я сравню BGW с Task.Run и покажу, насколько это громоздко в любой ситуации; есть некоторые ситуации, когда это немного более громоздко, но есть и другие ситуации, когда это гораздо более громоздко.

Теперь, являются ли они устаревшими в .NET 4.0, это еще один вопрос. Из ваших других сообщений вы говорите о разработке для .NET 4.0 с VS2010, поэтому backport Microsoft.Bcl.Async не является вариантом. В этом случае ни APM, ни EAP не могут считаться устаревшими ИМО. На этой платформе вы можете рассматривать Task.Factory.StartNew как альтернативу BGW, но BGW имеет некоторые преимущества, когда дело доходит до отчетов о ходе выполнения и автоматического марширования потоков событий и событий завершения.

Обновление: Недавно я обновил старый блог, где я обсуждает различные реализации фоновых операций, В этом сообщении, когда я говорю о "Задачах (Async Methods)", я имею в виду использование Task со всей поддержкой .NET 4.5 async, Task.Run и т.д. В разделе "Задачи (параллельная библиотека задач)" оценивая Task, как он существовал в .NET 4.0.

Ответ 3

Мой вопрос:

Не было ли это .NET 4.0 TPL, который сделал APM, EAP и BackgroundWorker асинхронные шаблоны устарели?

то есть. сомнение, требующее подтверждения или отрицания, что если что-то устарело в .NET 4.5, то я не понимаю, почему и как это может быть другим в .NET 4.0, то есть с параллельной библиотекой задач (без участия async/await и .NET 4.5 поддержка исключительно)

И я очень рад найти ответы Ник Поляк в своей статье о кодепроекте "Задача параллельной библиотеки и функциональность асинхронного ожидания - шаблоны использования в простых образцах" указано:

  • "Большая часть функциональных возможностей .NET 4.5 была изменена на" Задачи ", а BackgroundWorker сейчас устарела,, но вы все равно можете столкнуться с некоторыми примерами, когда необходимо использовать шаблон EAP. В таких случаях было бы неплохо создать объекты Task из функциональности EAP, чтобы вы могли планировать их параллельную работу или ждать завершения ряда предыдущих задач и т.д. В этом разделе мы покажем, как достичь этого"
  • "После того, как функция" Задача "сделала функциональность BackgroundWorker более крупной устаревшей, вы можете добиться того, что вам нужно, используя задачу вместо BackgroundWorker. Тем не менее, возможно, есть причина, по которой вы хотите использовать функциональность BackgroundWorker в команде - независимо от того, использует ли это ваш устаревший код или потому, что большинство членов вашей команды или вашего босса любят его и понимают лучше, чем новая функциональность"

все еще открыт для просмотра любых различных аргументированных точек зрения