Подтвердить что ты не робот

Шаблон дизайна, альтернативный Corouts

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

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

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

Спасибо

Update:

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

Update:

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

  • Огромное количество задач
  • Если задача блокирует следующую задачу в очереди, выполните
  • Отсутствие цикла процессора cpu
  • Минимальное переключение между задачами
4b9b3361

Ответ 1

Вы можете моделировать совместное микропоточность с помощью 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);
}

Компилятор С# разворачивает это в конечный автомат, но внешний вид представляет собой кооперативный микропроход.

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

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

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

Ответ 3

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

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

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

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

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

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

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

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

PS: У вас есть какие-то зависимости от порядка задач?

Ответ 4

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

Ответ 5

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

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

Ответ 6

Написание его для использования Async IO может быть достаточным.

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

Ответ 7

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

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

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

Ответ 9

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

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

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

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

Ответ 10

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

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