Вопрос

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

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

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

Спасибо

Обновить:

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

Обновить:

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

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

Решение

Вы можете моделировать совместную микропоточность с помощью 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 — у них есть несколько онлайн-документов, которые объясняют ее с точки зрения пользователя.

Другие советы

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

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

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

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

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

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

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

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

ПС:Есть ли у вас какие-либо зависимости от порядка выполнения задач?

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

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

Взгляните на такие шаблоны, как Reactor. здесь

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

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

Вам стоит взглянуть на это:

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

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

Еще немного информации о шаблоне «Реактивный» (как упоминалось другим автором) применительно к реализации в .NET;также известный как «Linq to Events»

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

-Ойсин

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top