У меня есть три зерна (A, B и C), выполняющие разные работы в конвейере. GrainA передаст результат в GrainB, а GrainB передаст результат в GrainC. Я хочу гарантировать последовательную отправку сообщений между последовательными зернами, что может быть достигнуто ниже.
// client code
foreach(var i in list)
{
await grainA.job(i);
}
//grain A code
async Task job(var i)
{
DoSomeWorkA(i);
await grainB.job(i);
}
//grain B code
async Task job(var i)
{
DoSomeWorkB(i);
await grainC.job(i);
}
//grain C code
async Task job(var i)
{
DoSomeWorkC(i);
Console.WriteLine(i);
}
Однако проблема этого кода в том, что нет конвейерной обработки. GrainA получает сетевой объект только тогда, когда текущий объект проходит через все GrainB и GrainC (из-за оператора await). Один из способов получить конвейерную обработку - не использовать await и напрямую отправлять объекты один за другим. Однако это приводит к доставке вне очереди, как описано в эта почта.
Я хочу, чтобы выполнение было полностью конвейерным, что означает, что GrainA переходит к своему следующему заданию, когда GrainB получает результат от GrainA. Однако порядок сообщений также важен, поскольку я отправляю некоторые управляющие сообщения. Как это сделать в Орлеане?
Думайте об этом как о рабочем процессе, в котором задействованы разные операторы, которые должны выполняться один за другим. Оператор добавляет к объекту что-то, что может понадобиться последующим операторам. Следовательно, операторы должны работать последовательно, но конвейерно. то есть, когда объект-1 находится в зерне C, объект-2 должен быть в зерне B, объект-3 - в зерне A.
Эти операторы сохраняют состояние? Почему порядок возврата имеет значение, если они просто полностью возвращаются вверх по стеку исходному вызывающему абоненту? Если есть какая-то последовательность, которая важна, ее нет в приведенном выше коде.
Чтобы упростить программирование и рассуждение систем с высокой степенью параллелизма, Орлеан ограничивает параллелизм и параллелизм каждого зерна (единицы вычисления и состояния) до 1. Это означает, что одновременно будет обрабатываться не более одного сообщения и не более одного потока. будет выполнять код активации заданного зерна в любой момент времени.
Это значительно упрощает то, на чем разработчик должен сосредоточиться, поскольку больше нет необходимости в примитивах синхронизации потоков, таких как блокировки.
Однако поведение можно настроить. Если вам не нужно это «безопасное» поведение по умолчанию, то вы можете пометить зерно как [Reentrant]
или пометить отдельные методы как [AlwaysInterleave]
в интерфейсе зерна.
Это позволяет обрабатывать множество сообщений одновременно. В каждой точке await
в методе зернистости выполнение возвращается планировщику, и планировщик может начать обработку другого сообщения. Планировщик по-прежнему обеспечивает однопоточность для каждой активации зерна, поэтому блокировки по-прежнему не нужны, но сообщениям разрешено чередование в этих точках await
(то есть совместная многозадачность). Это означает, что разработчику теперь необходимо рассмотреть, как внутреннее состояние может быть изменено другими запросами между этими точками await
.
Для получения дополнительной информации см. Страница повторного входа в документации Орлеана.
Чтобы увеличить параллелизм (для задач, связанных с процессором), разработчик может использовать Работники без гражданства или Внешние задачи через Task.Run
или аналогичный.
Также обратите внимание, что сообщения, отправленные от активации A до активации B, всегда будут отправляться по порядку независимо от конфигурации параллелизма. То же самое и для результатов, отправленных от B к A. Обратите внимание, что по-прежнему становится труднее рассуждать о порядке сообщений после увеличения параллелизма, поскольку второй вызов B может завершиться до первого вызова и, следовательно, его результат будет отправлен раньше, также. Это означает, что результаты могут быть получены не по порядку. Я полагаю, что это то, чего вы ожидаете.
Ой. это может вызвать проблему. Пожалуйста, скажите мне, правильно ли я понимаю ниже. Assume I use [Reentrant]. If grain-A makes request-1 to grain-B and awaits that, then it is free to make request-2 too to grain-B. Now in grain-B, the request-2 might arrive earlier than request-1. Thus, the message order is no more intact.
Нет, я сказал, что заявки всегда в порядке. Это ответы, которые могут быть не по порядку, потому что обработка запросов может занять разное время (и я предполагаю, что вы делаете некоторое ожидание, чтобы выполнение было выполнено, и в запросе может быть неупорядоченное чередование). Какого поведения вы пытаетесь добиться? У этих зерен есть какое-то состояние? Другими словами, можете ли вы выполнить горизонтальное масштабирование, используя работники без сохранения состояния или несколько гранул на каждом этапе?
Сообщите мне, если это ответит на ваш вопрос. Мне интересно лучше понять ваш сценарий.
Мы пытаемся разработать механизм обработки рабочего процесса с использованием зерен. Рабочий процесс может содержать множество операторов, таких как операторы SQL или модели машинного обучения. Каждый оператор может быть представлен зерном (однако для параллелизма у нас может быть несколько зерен на оператор). Последовательность строк проходит через этот рабочий процесс, и операторы действуют в соответствии с ними. В настоящее время мы не видим, что операторы имеют состояние (но еще рано говорить, что состояние нам не понадобится).
Мы ничего не возвращаем предыдущим операторам (поскольку строки перемещаются вперед в рабочем процессе и, наконец, помещаются в поток). Итак, нас интересует выполнение запросов у вызываемого объекта в том же порядке, в котором они вызываются вызывающим. Однако уверены ли вы в том, что запросы в порядке? Потому что в предыдущий вопрос мы видели, что выполнение на уровне зерна не было в порядке вызова на клиенте (то есть последовательность сообщений не сохраняется Орлеаном). Или ожидание меняет это поведение?
Порядок сохраняется, однако, если вы отправляете сообщения 1, 2 и 3 в зерно, возможно, что бункер, который он активирует при сбоях после сообщения 2, и зернистость повторно активируется для сообщения 3. Не ожидая каждого сообщения, вы можете гарантировать, что 1 и 2 обрабатываются до 3. Сообщения в несуществующий бункер не будут повторно отправляться автоматически. Поэтому я бы предложил либо не включать чередование (и, следовательно, обрабатывать одно сообщение за раз), либо использовать потоки для достижения желаемого поведения, либо использовать настраиваемое решение (возможно, включающее порядковые номера), чтобы гарантировать, что каждое зерно в графе обрабатывает сообщения в
Опечатка: не ожидая каждого сообщения, вы можете нет гарантировать, что 1 и 2 обрабатываются раньше 3
Таким образом, вы не хотите, чтобы разные задания выполнялись одновременно, но вы не хотите, чтобы они выполнялись последовательно. Так чего ты хочешь? Какие операции можно переупорядочивать друг относительно друга, а какие нельзя?