Понимание однопоточной природы зерен Орлеана - PullRequest
0 голосов
/ 29 ноября 2018

У меня есть следующий фрагмент кода клиента и зерна в Орлеане. (Хотя рекомендуемый способ разработки в Орлеане - это дождаться Задачи, в некоторых местах следующий код не ожидает только для экспериментов)

// 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.Если я могу получить помощь от этого сообщения о потоке стека , то, что здесь происходит, это то, что:

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

Мой первый вопрос - правильно ли я описал, что происходит в коде, особенно о том, что поток не возвращается в пул потоков при достижении ожидания в функции 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 и не выпускает его)?

Ответы [ 2 ]

0 голосов
/ 05 декабря 2018

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

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

0 голосов
/ 05 декабря 2018

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

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

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

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

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

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

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

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

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

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

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

...