Понимание однопоточной природы зерна Орлеана

У меня есть следующий фрагмент кода клиента и зерна в Орлеане.

// client code
while(true)
{
    Console.WriteLine("Client giving another request");   
    double temperature = random.NextDouble() * 40;   
    var grain = client.GetGrain<ITemperatureSensorGrain>(500);
    Task t = sensor.SubmitTemperatureAsync((float)temperature);
    Console.WriteLine("Client Task Status - "+t.Status);
    await Task.Delay(5000);
}

// ITemperatureSensorGrain code
public async Task SubmitTemperatureAsync(float temperature)
{
   long grainId = this.GetPrimaryKeyLong();
   Console.WriteLine($"{grainId} outer received temperature: {temperature}");

   Task x = SubmitTemp(temperature); // SubmitTemp() is another function in the same grain
   x.Ignore();
   Console.WriteLine($"{grainId} outer received temperature: {temperature} exiting");
}

public async Task SubmitTemp(float temp)
{
    for(int i=0; i<1000; i++)
    {
       Console.WriteLine($"Internal function getting awaiting task {i}");
       await Task.Delay(1000);
    }
}

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

Client giving another request
Client Task Status - WaitingForActivation
500 outer received temperature: 23.79668
Internal function getting awaiting task 0
500 outer received temperature: 23.79668 exiting
Internal function getting awaiting task 1
Internal function getting awaiting task 2
Internal function getting awaiting task 3
Internal function getting awaiting task 4
Client giving another request
Client Task Status - WaitingForActivation
500 outer received temperature: 39.0514
Internal function getting awaiting task 0  <------- from second call to SubmitTemp
500 outer received temperature: 39.0514 exiting
Internal function getting awaiting task 5  <------- from first call to SubmitTemp
Internal function getting awaiting task 1
Internal function getting awaiting task 6
Internal function getting awaiting task 2
Internal function getting awaiting task 7
Internal function getting awaiting task 3
Internal function getting awaiting task 8
Internal function getting awaiting task 4
Internal function getting awaiting task 9

Вывод имеет смысл с точки зрения нормального приложения.Net. Если я смогу получить помощь от этого сообщения stackru, то, что здесь происходит, это то, что:

  1. Клиент звонит ITemperatureSendorGrain и продвигается вперед. Когда await ударил, клиентский поток возвращается в пул потоков.
  2. SubmitTemperatureAsync получает запрос и вызывает локальную асинхронную функцию SubmitTemp,
  3. SubmitTemp выводит оператор, соответствующий i=0, после которого он попадает в ожидание. Жду остальное for loop быть запланированным как продолжение ожидаемого (Task.Delay) и элемент управления возвращается к вызывающей функции SubmitTemperatureAsync, Обратите внимание, что поток не возвращается в пул потоков, когда он встречает, ожидает в SubmitTemp функция. Управление потоком фактически возвращается к вызывающей функции SubmitTemperatureAsync, Итак, turn, как определено в документации Орлеана, заканчивается, когда метод верхнего уровня встречает ожидание. Когда ход заканчивается, поток возвращается в пул потоков.
  4. Вызывающая функция не ждет завершения задачи и завершается.
  5. Когда ожидаемое в SubmitTemp возвращается через 1 с, он получает поток из пула потоков и планирует остальные for loop в теме.
  6. Когда возвращается ожидаемый в клиентском коде, он делает еще один вызов того же зерна и еще один раунд for loop запланировано в соответствии со вторым вызовом SubmitTemp,

Мой первый вопрос: правильно ли я описал то, что происходит в коде, особенно о том, что поток не возвращается в пул потоков, когда в функцию попадает await SubmitTemp,


Согласно однопоточному характеру зерен, в любой момент времени только один поток будет выполнять код зерна. Кроме того, как только запрос к зерну начинает выполнение, он будет полностью завершен до того, как будет принят следующий запрос (вызванный chunk based execution в документах Орлеана). На высоком уровне это верно для приведенного выше кода, потому что следующий вызов SubmitTemperatureAsync произойдет только при выходе из текущего вызова метода.

Тем не мение, SubmitTemp был на самом деле подфункцией SubmitTemperatureAsync, Хотя SubmitTemperatureAsync вышел, SubmitTemp все еще выполняется, и пока он это делает, Орлеан разрешил еще один вызов SubmitTemperatureAsync выполнить. Разве это не нарушает однопоточный характер зерна Орлеана - мой второй вопрос?


Считают, что SubmitTemp в его for loop необходимо получить доступ к некоторым членам данных класса зерна. Итак ExecutionContext будет захвачен при ожидании и когда Task.Delay(1000) возвращается, захваченный ExecutionContext будут переданы в расписание остальной for loop на волоске Так как ExecutionContext пройдено, остаток for loop сможет получить доступ к членам данных, несмотря на то, что работает в другом потоке. Это то, что происходит в любом обычном асинхронном приложении.Net.

Мой третий вопрос о SynchronizationContext, Я сделал краткий поиск в репозитории Орлеана, но не смог найти какую-либо реализацию SynchronizationContext.Post()что приводит меня к мысли, что нет SynchronizationContext требуется для запуска методов Орлеана. Кто-нибудь может это подтвердить? Если это не так, и SynchronizationContext требуется, не будет ли параллельное выполнение различных вызовов SubmitTemp (как показано в приведенном выше коде), рискуйте оказаться в тупике (если кто-то держится за SynchronizationContext и не выпускает его)?

0 ответов

Вопрос 1: Является ли описанный поток выполнения точным представлением того, что происходит?

Ваше описание выглядит примерно правильно для меня, но вот несколько тонких моментов:

  • Есть ли пул потоков или нет - это деталь реализации.
  • "Повороты" - это каждая синхронная часть работы, запланированная на активацию TaskScheduler,
  • Следовательно, ход заканчивается, когда выполнение должно быть возвращено обратно TaskScheduler,
  • Это может быть потому, что await попадание, которое не было завершено синхронно, или, возможно, пользователь не использует await вообще и программирует с помощью ContinueWith или обычай ждет.
  • Поворот можно завершить с помощью метода не верхнего уровня, например, если код был изменен на await SubmitTemp(x) вместо .Ignoring() это, тогда поворот закончится, когда Task.Delay(...) был поражен внутри SubmitTemp(x),

Вопрос 2: демонстрирует ли пример программы нарушение однопоточной гарантии?

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

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

.NET позволяет дочерним задачам быть прикрепленными к их родительским задачам. В этом случае родительская задача завершается только после завершения всех дочерних задач. Однако это не стандартное поведение, и обычно рекомендуется избегать включения этого поведения (например, передавая TaskCreationOptions.AttachedToParent в Task.Factory.StartNew).

Если вы использовали это поведение (пожалуйста, не надо), то вы увидите цикл активации при первом вызове SubmitTemp() на неопределенный срок, и больше сообщений не будет обрабатываться.

Вопрос 3: Орлеан использует SynchronizationContext?

Орлеан не использует SynchronizationContext, Вместо этого он использует обычай TaskScheduler Реализации. Увидеть ActivationTaskScheduler.cs, Каждая активация имеет свою ActivationTaskScheduler и все сообщения являются планировщиком, использующим этот планировщик.

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

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

Вот почему я хотел бы подчеркнуть, что рекомендуемый способ написания кода зерна - это ждать каждого Task в стеке вызовов. В приведенном выше коде это будет означать ожидание x в методе зерна и t в коде клиента. По умолчанию зерна не реентерабельны, и это может помешать второму вызову от клиента начать выполнение до завершения первого. Или можно выбрать, чтобы пометить класс зерна как [Reentrant] и разрешить чередование второго вызова. Это было бы намного более ясным и явным, чем фоновый цикл, и сделало бы возможным обработку ошибок.

Другие вопросы по тегам