C # Асинхронные параметры для обработки списка - PullRequest
34 голосов
/ 06 сентября 2011

Я пытаюсь лучше понять параметры Async и Parallel, которые есть в C #. В приведенные ниже фрагменты я включил 5 подходов, с которыми сталкиваюсь чаще всего. Но я не уверен, какой выбрать - или еще лучше, какие критерии следует учитывать при выборе:

Метод 1: Задание

(см. http://msdn.microsoft.com/en-us/library/dd321439.aspx)

Вызов StartNew функционально эквивалентен созданию Задачи с использованием одного из ее конструкторов, а затем вызову Start, чтобы запланировать ее выполнение. Однако, если создание и планирование не должны быть разделены, StartNew является рекомендуемым подходом как для простоты, так и для производительности.

Метод StartNew в TaskFactory должен быть предпочтительным механизмом для создания и планирования вычислительных задач, но для сценариев, где создание и планирование должны быть разделены, могут использоваться конструкторы, а метод Start задачи может затем использоваться для планирования задача для выполнения в более позднее время.

// using System.Threading.Tasks.Task.Factory
void Do_1()
{
    var _List = GetList();
    _List.ForEach(i => Task.Factory.StartNew(_ => { DoSomething(i); }));
}

Метод 2: QueueUserWorkItem

(см. http://msdn.microsoft.com/en-us/library/system.threading.threadpool.getmaxthreads.aspx)

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

Вы можете поместить данные, требуемые методом очереди, в поля экземпляра класса, в котором определен метод, или использовать перегрузку QueueUserWorkItem (WaitCallback, Object), которая принимает объект, содержащий необходимые данные.

// using System.Threading.ThreadPool
void Do_2()
{
    var _List = GetList();
    var _Action = new WaitCallback((o) => { DoSomething(o); });
    _List.ForEach(x => ThreadPool.QueueUserWorkItem(_Action));
}

Метод 3: Параллельное. Движение

(см .: http://msdn.microsoft.com/en-us/library/system.threading.tasks.parallel.foreach.aspx)

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

Делегат тела вызывается один раз для каждого элемента в перечисляемом источнике. Он предоставляется с текущим элементом в качестве параметра.

// using System.Threading.Tasks.Parallel
void Do_3()
{
    var _List = GetList();
    var _Action = new Action<object>((o) => { DoSomething(o); });
    Parallel.ForEach(_List, _Action);
}

Метод 4: IAsync.BeginInvoke

(см .: http://msdn.microsoft.com/en-us/library/cc190824.aspx)

BeginInvoke является асинхронным; следовательно, управление немедленно возвращает вызывающему объекту после его вызова.

// using IAsync.BeginInvoke()
void Do_4()
{
    var _List = GetList();
    var _Action = new Action<object>((o) => { DoSomething(o); });
    _List.ForEach(x => _Action.BeginInvoke(x, null, null));
}

Метод 5: BackgroundWorker

(см .: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx)

Чтобы настроить фоновую операцию, добавьте обработчик события для события DoWork. Вызовите трудоемкую операцию в этом обработчике событий. Чтобы начать операцию, вызовите RunWorkerAsync. Чтобы получать уведомления об обновлениях, обработайте событие ProgressChanged. Чтобы получить уведомление о завершении операции, обработайте событие RunWorkerCompleted.

// using System.ComponentModel.BackgroundWorker
void Do_5()
{
    var _List = GetList();
    using (BackgroundWorker _Worker = new BackgroundWorker())
    {
        _Worker.DoWork += (s, arg) =>
        {
            arg.Result = arg.Argument;
            DoSomething(arg.Argument);
        };
        _Worker.RunWorkerCompleted += (s, arg) =>
        {
            _List.Remove(arg.Result);
            if (_List.Any())
                _Worker.RunWorkerAsync(_List[0]);
        };
        if (_List.Any())
            _Worker.RunWorkerAsync(_List[0]);
    }
}

Полагаю, очевидными критериями были бы:

  1. Лучше других по производительности?
  2. Что лучше, чем другие, для обработки ошибок?
  3. Что лучше, чем другие, для мониторинга / обратной связи?

Но как вы выбираете? Заранее благодарим за ваши идеи.

Ответы [ 4 ]

15 голосов
/ 06 сентября 2011

Собираю их в произвольном порядке:

BackgroundWorker (# 5)
Мне нравится использовать BackgroundWorker, когда я делаю что-то спользовательский интерфейс.Его преимущество заключается в том, что события потока и завершения запускаются в потоке пользовательского интерфейса, что означает, что вы не получите неприятных исключений при попытке изменить элементы пользовательского интерфейса.У этого также есть хороший встроенный способ сообщить о прогрессе.Одним из недостатков этого режима является то, что если в вашей работе блокируются вызовы (например, веб-запросы), у вас будет поток, который бездействует, ничего не делая во время работы.Это, вероятно, не проблема, если вы думаете, что у вас их будет всего несколько.

IAsyncResult / Begin / End (APM, # 4)
Это широко распространенная и мощная, но сложная в использовании модель.Обработка ошибок является проблематичной, поскольку вам необходимо повторно перехватывать исключения в вызове End, а неперехваченные исключения не обязательно возвращают его в какие-либо соответствующие фрагменты кода, которые могут его обработать.Это может привести к постоянному зависанию запросов в ASP.NET или просто к тому, что ошибки загадочно исчезают в других приложениях.Вы также должны быть бдительными в отношении свойства CompletedSynchronously.Если вы не отследите и не сообщите об этом должным образом, программа может зависнуть и пропустить ресурсы.С другой стороны, если вы работаете в контексте другого APM, вы должны убедиться, что все асинхронные методы, которые вы вызываете, также сообщают об этом значении.Это означает выполнение другого вызова APM или использование Task и приведение его к IAsyncResult для получения его свойства CompletedSynchronously.

В сигнатурах также много накладных расходов: вы должны поддерживатьПроизвольный объект для прохождения, создайте собственную реализацию IAsyncResult, если вы пишете асинхронный метод, который поддерживает опросы и дескрипторы ожидания (даже если вы используете только обратный вызов).Кстати, вы должны использовать только обратный вызов здесь.Когда вы используете дескриптор ожидания или опрос IsCompleted, вы теряете поток, пока выполняется операция.

Асинхронный шаблон на основе событий (EAP)
Тот, которого не было в вашем списке, но я упомяну для полноты картины.Это немного дружелюбнее, чем APM.Вместо обратных вызовов есть события, и на сигнатурах методов висит меньше мусора.Обработка ошибок немного проще, поскольку она сохраняется и доступна в обратном вызове, а не повторно генерируется.CompletedSynchronously также не является частью API.

Задачи (# 1)
Задачи - это еще один дружественный асинхронный API.Обработка ошибок проста: исключение всегда существует для проверки обратного вызова, и никто не заботится о CompletedSynchronously.Вы можете создавать зависимости, и это отличный способ справиться с выполнением нескольких асинхронных задач.Вы даже можете обернуть в них асинхронные методы APM или EAP (один из которых вы пропустили).Еще одна хорошая вещь об использовании задач - ваш код не заботится о том, как реализована операция.Он может блокироваться в потоке или быть полностью асинхронным, но потребляющий код не заботится об этом.Вы также можете легко смешивать операции APM и EAP с Задачами.

Parallel.For методы (# 3)
Это дополнительные помощники поверх Задач.Они могут выполнить часть работы, чтобы создать задачи для вас и сделать ваш код более читабельным, если ваши асинхронные задачи подходят для выполнения в цикле.

ThreadPool.QueueUserWorkItem (# 2)
Это утилита низкого уровня, которая на самом деле используется ASP.NET для всех запросов.Он не имеет встроенной обработки ошибок, такой как задачи, поэтому вам нужно все перехватить и передать обратно в ваше приложение, если вы хотите узнать об этом.Он подходит для работы с интенсивным использованием процессора, но вы не хотите делать какие-либо блокирующие вызовы, такие как синхронный веб-запрос.Это потому, что пока он работает, он использует поток.

async / await Ключевые слова
Новое в .NET 4.5, эти ключевые слова позволяют писать асинхронный код без явных обратных вызовов.Вы можете ожидать на Task, и любой код ниже этого будет ожидать завершения этой асинхронной операции без использования потока.

4 голосов
/ 06 сентября 2011

В первом, третьем и четвертом примерах неявно используется ThreadPool, поскольку по умолчанию задачи планируются в ThreadPool, а расширения TPL также используют ThreadPool, а API просто скрывает некоторые сложности. здесь .BackgroundWorkers являются частью пространства имен ComponentModel, поскольку они предназначены для использования в сценариях пользовательского интерфейса.

2 голосов
/ 06 сентября 2011

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

Это не нативная версия, однако она разработана лабораториями Ms.Он доступен как для .NET 3.5, так и .NET 4.0 и, по сути, представляет собой набор методов расширения для представленного в .NET 4.0 интерфейса IObservable<T>.

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

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

Основной сайт: http://msdn.microsoft.com/en-us/data/gg577609

Руководство для начинающих: http://msdn.microsoft.com/en-us/data/gg577611

Примеры:http://rxwiki.wikidot.com/101samples

Тем не менее, лучший асинхронный шаблон, вероятно, зависит от того, в какой ситуации вы находитесь. Некоторые из них лучше (проще) для более простых вещей, а некоторые более расширяемы и проще в обращении, когда речь идет о более сложныхсценарии.Я не могу говорить за всех тех, кого вы упомянули.

0 голосов
/ 06 сентября 2011

Последний лучший на 2,3 как минимум. Для этого есть встроенные методы / свойства. Другие варианты почти одинаковы, просто разные версии / удобные обертки

...