Шаблон дизайна Альтернатива сопрограмм - PullRequest
12 голосов
/ 23 августа 2009

В настоящее время у меня есть большое количество C # вычислений (вызовов методов), находящихся в очереди, которая будет выполняться последовательно. Каждое вычисление будет использовать некоторый сервис с высокой задержкой (сеть, диск ...).

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

Существует ли шаблон проектирования, который можно реализовать в чистом C #, который позволит мне обрабатывать дополнительные вычисления в ожидании возврата служб с высокой задержкой?

Спасибо

Обновление:

Мне нужно выполнить огромное количество (> 10000) задач, и каждая задача будет использовать какой-либо сервис с высокой задержкой. В Windows вы не можете создавать столько потоков.

Обновление:

По сути, мне нужен шаблон проектирования, который имитирует преимущества (как показано ниже) тасклетов в Stackless Python (http://www.stackless.com/)

)
  1. Огромное количество заданий
  2. Если задача блокируется, следующая задача в очереди выполняется
  3. Нет потраченного впустую цикла процессора
  4. Минимальные накладные расходы на переключение между задачами

Ответы [ 10 ]

9 голосов
/ 24 августа 2009

Вы можете смоделировать совместную микропоточность, используя IEnumerable. К сожалению, это не будет работать с блокирующими API, поэтому вам нужно найти API, которые вы можете опрашивать или которые имеют обратные вызовы, которые вы можете использовать для сигнализации.

Рассмотрим метод

IEnumerable Thread ()
{
    //do some stuff
    Foo ();

    //co-operatively yield
    yield null;

    //do some more stuff
    Bar ();

    //sleep 2 seconds
    yield new TimeSpan (2000);
}

Компилятор C # развернет это в конечный автомат, но выглядит как кооперативная микропотока.

Шаблон довольно прост. Вы реализуете «планировщик», который хранит список всех активных IEnumerators. Когда он циклически перебирает список, он «запускает» каждый из них с помощью MoveNext (). Если значение MoveNext равно false, поток завершился, и планировщик удаляет его из списка. Если это так, то планировщик обращается к свойству Current, чтобы определить текущее состояние потока. Если это TimeSpan, поток хочет перевести в спящий режим, и планировщик переместил его в некоторую очередь, которую можно сбросить обратно в основной список после окончания временных интервалов спящего режима.

Вы можете использовать другие возвращаемые объекты для реализации других механизмов сигнализации. Например, определите какой-нибудь WaitHandle. Если поток выдаст один из них, он может быть перемещен в очередь ожидания до тех пор, пока дескриптор не будет сигнализирован. Или вы можете поддержать WaitAll, получив массив дескрипторов ожидания. Вы могли бы даже реализовать приоритеты.

Я сделал простую реализацию этого планировщика примерно в 150LOC, но я еще не дошел до блога кода. Это было для нашей обертки PhyreSharp PhyreEngine (которая не будет общедоступной), где она, кажется, работает очень хорошо для управления парой сотен символов в одной из наших демонстраций. Мы заимствовали эту концепцию из движка Unity3D - у них есть несколько онлайн-документов, объясняющих ее с точки зрения пользователя.

5 голосов
/ 24 августа 2009

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

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

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

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

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

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

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

Надеюсь, это имеет смысл!

PS: Есть ли у вас зависимости от порядка задач?

2 голосов
/ 26 августа 2009

Вы обязательно должны проверить Параллельное и координационное время выполнения . Один из их примеров описывает именно то, о чем вы говорите: вы вызываете сервисы с большими задержками, а CCR эффективно позволяет выполнять еще одну задачу, пока вы ждете. Он может выполнять огромное количество задач, потому что ему не нужно создавать поток для каждой, хотя он будет использовать все ваши ядра, если вы об этом попросите.

1 голос
/ 23 ноября 2010

Вы должны взглянуть на это:

http://www.replicator.org/node/80

Это должно делать именно то, что вы хотите. Это взлом, однако.

1 голос
/ 23 августа 2009

Запись его для использования Асинхронный ввод-вывод может быть достаточно.

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

1 голос
/ 23 августа 2009

Разве это не обычное использование многопоточной обработки?

Посмотрите на такие шаблоны, как Reactor здесь

0 голосов
/ 20 февраля 2014

Да, конечно, вы можете. Вам просто нужно создать механизм диспетчера, который будет перезванивать на предоставляемой вами лямбде и помещаться в очередь. Весь код, который я пишу в единстве, использует этот подход, и я никогда не использую сопрограммы. Я обертываю методы, которые используют сопрограммы, такие как WWW, чтобы избавиться от них. В теории сопрограммы могут быть быстрее, потому что меньше накладных расходов. Практически они вводят новый синтаксис в язык для выполнения довольно тривиальной задачи, и, кроме того, вы не можете правильно отслеживать трассировку стека при ошибке в подпрограмме, потому что все, что вы увидите -> Далее. Затем вам нужно будет реализовать возможность запуска задач в очереди в другом потоке. Тем не менее, в последних версиях .net есть параллельные функции, и вы, по сути, будете писать аналогичные функции. Это не было бы много строк кода на самом деле.

Если кому-то будет интересно, я отправлю код, но у меня его нет.

0 голосов
/ 28 декабря 2013

Фактически, если вы используете один поток для выполнения задачи, вы потеряете игру. Подумайте, почему Node.js может поддерживать огромное количество соединений. Использование нескольких потоков с асинхронным вводом-выводом !!! Асинхронные и ожидающие функции могут помочь в этом.

foreach (var task in tasks)
{
    await SendAsync(task.value);
    ReadAsync(); 
}

SendAsync () и ReadAsync () являются поддельными функциями для асинхронного вызова ввода-вывода.

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

0 голосов
/ 24 августа 2009

Еще немного информации о шаблоне "Reactive" (как упомянуто другим автором) относительно реализации в .NET aka "Linq to Events"

http://themechanicalbride.blogspot.com/2009/07/introducing-rx-linq-to-events.html

-Oisin

...