Результат вызова задачи на зерне в Орлеане

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


Первый тест включал отправку запроса от клиента к определенному зерну каждую 1 секунду, в то время как зерно требовало 10 секунд для выполнения запросов. Код такой:

// client code
while (1)
{
    Console.WriteLine("Client giving another request");
    double temperature = random.NextDouble() * 40;
    var sensor = client.GetGrain<ITemperatureSensorGrain>(500);
    Task t = sensor.SubmitTemperatureAsync((float)temperature);
    Console.WriteLine(t.Status);
    Thread.Sleep(1000);
 }

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

    Console.WriteLine($"{grainId} outer complete");
    return Task.CompletedTask;
}

Вывод консоли:

Client giving another request
Task Status - WaitingForActivation
500 outer received temperature: 32.29987    <------------ print statement inside grain
Client giving another request     <--------------------- client continues
Task Status - WaitingForActivation  <------------------- client isn't blocked
Client giving another request
Task Status - WaitingForActivation
Client giving another request
Task Status - WaitingForActivation
Client giving another request
Task Status - WaitingForActivation
Client giving another request
Task Status - WaitingForActivation
Client giving another request
Task Status - WaitingForActivation
Client giving another request
Task Status - WaitingForActivation
Client giving another request
Task Status - WaitingForActivation
Client giving another request
Task Status - WaitingForActivation
Client giving another request
Task Status - WaitingForActivation
500 outer complete

Поскольку зерна в Орлеане являются однопоточными, вызывается только первый запрос, а остальные запросы ставятся в очередь на стороне зерна. Мои вопросы по этой части: -

В обычном C #, когда вызывается асинхронный метод, он продолжает работу в основном потоке, пока не встретит оператор await, когда он запускает ожидаемое выражение как другую задачу и возвращает эту задачу. Таким образом, вызывающий блокируется до тех пор, пока не сработает оператор ожидания. Точно так же и здесь клиент должен быть заблокирован на 10 секунд, после чего первый запрос на зернистость вернет Задачу. Однако этого не происходит. Клиент продолжает планировать задачи без блокировки.

  • Итак, вызов зерна от клиента FireAndForget?
  • Если да, то как им вернуть объект Task?
  • Есть ли какая-либо блокировка, когда клиент обращается к объекту зерна, а среда выполнения возвращает объект Task обратно клиенту?

Второй тест включал отправку запроса от зерна к зерну, в котором второе зерно ожидает 10 секунд, прежде чем вернуться. Код такой:

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

    // make client sleep for a long time after the first request
    // because we don't want any more requests from the client
    Thread.Sleep(1000000000);
}

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

    while(true)
    {
        Console.WriteLine("Grain sending another request");
        ITempBGrain sensor = this.GrainFactory.GetGrain<ITempBGrain>(400);
        // await sensor.SubmitTempBAsync(temperature);
        Task t = sensor.SubmitTempBAsync(temperature);
        Console.WriteLine("Grain Task Status - "+t.Status);
        Thread.Sleep(1000);
    }
}

// inner-grain (ITempBGrain) code
public Task SubmitTempBAsync(float temperature)
{
    long grainId = this.GetPrimaryKeyLong();
    Console.WriteLine($"{grainId} internal received temperature: {temperature}");
    Thread.Sleep(10000);
    Console.WriteLine($"{grainId} internal complete");
    return Task.CompletedTask;
}

Вывод консоли:

Client giving another request
Client Task Status - WaitingForActivation
500 outer received temperature: 10.36764
Grain sending another request       <-------------- Outer grain prints
Grain Task Status - WaitingForActivation 
Grain sending another request  <----------- Inner grain doesn't print
Grain Task Status - WaitingForActivation
Grain sending another request
Grain Task Status - WaitingForActivation
Grain sending another request
Grain Task Status - WaitingForActivation
Grain sending another request
Grain Task Status - WaitingForActivation
Grain sending another request
Grain Task Status - WaitingForActivation
Grain sending another request
Grain Task Status - WaitingForActivation
Grain sending another request
Grain Task Status - WaitingForActivation
Grain sending another request
Grain Task Status - WaitingForActivation
Grain sending another request
Grain Task Status - WaitingForActivation
Grain sending another request
Grain Task Status - WaitingForActivation
Grain sending another request
Grain Task Status - WaitingForActivation
Grain sending another request
Grain Task Status - WaitingForActivation
Grain sending another request
Grain Task Status - WaitingForActivation
Grain sending another request
Grain Task Status - WaitingForActivation
Grain sending another request
Grain Task Status - WaitingForActivation
Grain sending another request
Grain Task Status - WaitingForActivation
Grain sending another request
Grain Task Status - WaitingForActivation
Grain sending another request
Grain Task Status - WaitingForActivation
warn: Orleans.Runtime.CallbackData[100157]
      Response did not arrive on time in 00:00:30 for message: Request *cli/015ba7a5@d4cdc7ab->S127.0.0.1:30000:0*grn/6424EE47/000001f4 #17: . Target History is: <S127.0.0.1:30000:0:*grn/6424EE47/000001f4:>. About to break its promise.
Grain sending another request
Grain Task Status - WaitingForActivation

Я вижу здесь нечто похожее на то, что произошло с клиентом в первом эксперименте. Итак, эти вопросы все еще существуют. Однако здесь происходит еще одна странная вещь. Консольный вывод внутреннего зерна нигде не появляется. Почему не выполняется внутренняя зернистость? Если я включаю закомментированную строку в коде внешней зернистости и ожидаю выполнения задачи внутренней зернистости, появляется следующий вывод, который кажется допустимым.

Client giving another request
Client Task Status - WaitingForActivation
500 outer received temperature: 6.332514
Grain sending another request
400 internal received temperature: 6.332514
400 internal complete
Grain sending another request
400 internal received temperature: 6.332514

person AvinashK    schedule 28.07.2018    source источник
comment
Вы не ждете, что задача Task t = sensor.SubmitTemperatureAsync((float)temperature); должна быть await sensor.SubmitTemperatureAsync((float)temperature); или позвонить await t куда-нибудь после.   -  person Tseng    schedule 30.07.2018
comment
Я не думаю, что проблема в этом. Как только задача отправлена ​​зерну в Орлеане (в данном случае внутреннему зерну), внешнему зерну не нужно его ждать. Задача во внутреннем зерне должна выполняться несмотря ни на что.   -  person AvinashK    schedule 30.07.2018
comment
Я думаю, вы не понимаете, как работает async. Орлеан - это асинхронное выполнение, поэтому все зерна возвращают Task или Task ‹T›. Thread.Sleep(10000); блокирует поток, Task.Delay(10000) - нет. Вдобавок Thread.Sleep(1000000000) это >, затем 00:30;)   -  person Tseng    schedule 30.07.2018
comment
Также находится ли клиентский код в том же приложении / процессе, что и зерна? Однако смешивание асинхронного и синхронизирующего кода недопустимо, оно требует проблем. Помните, что ожидание задачи - это не то же самое, что запуск нового потока (или выполнение кода в каком-то фоновом потоке [из пула])   -  person Tseng    schedule 30.07.2018
comment
Я смущен. Что я знаю, так это то, что в Орлеане каждое зерно работает в своем собственном потоке, отдельном от клиентского потока. Запрос от одного зерна к другому - это форма RPC. Ожидание просто используется для блокировки вызывающего потока зерна до тех пор, пока запрос в вызываемом зерне не завершится. Я не считаю это необходимым. Это может быть доказано первым тестом моего эксперимента, описанного выше, когда я не ожидаю в клиенте, но запросы выполняются в зернах. Кроме того, во второй части, если я изменяю и удаляю цикл while из внешнего зерна, все задачи выполняются правильно без ожидания ожидания.   -  person AvinashK    schedule 30.07.2018
comment
Кроме того, я знаю, что это плохая практика кодирования. Как я уже сказал, я делаю это для экспериментов.   -  person AvinashK    schedule 30.07.2018
comment
Нет-нет, распространенное заблуждение. Каждое зерно НЕ БЕГАЕТ ПО СВОЕЙ НИТИ. Орлеан гарантирует только то, что зерно будет обрабатываться только одним потоком (следовательно: для синхронизации потоков не требуется никакого механизма блокировки). Орлеан использует асинхронную функцию .NET Core. Он будет обрабатывать зерно до тех пор, пока не произойдет асинхронная операция. Затем он возвращает поток в пул потоков. Этот поток может использоваться другим зерном для обработки данных до завершения асинхронной операции. Когда он будет завершен, он возобновится. это не обязательно тот же поток (но его тот же контекст)   -  person Tseng    schedule 30.07.2018
comment
Затем поэкспериментируйте с полной асинхронностью. Асинхронный эквивалент Thread.Sleep(n) - _2 _ !!!   -  person Tseng    schedule 30.07.2018
comment
Спасибо, @Tseng! @Avinash Kumar, в вашем вопросе много частей, но я хочу сосредоточиться на одном: в обычном C #, когда вызывается метод async, он продолжает работу в основном потоке, пока не встретит оператор await, когда он запускает ожидаемое выражение как другое Задача и возвращает эту задачу. . Это по-прежнему верно для Орлеана, поскольку он работает на .NET, однако обратите внимание, что вы вызываете интерфейс, а не напрямую вызываете свой класс зерна. Если Орлеан заблокирует этот удаленный вызов до первого ожидания, ваша система будет чрезвычайно, чрезвычайно медленной и не сможет масштабироваться.   -  person Reuben Bond    schedule 31.07.2018
comment
@Tseng верен: зерна не работают в собственном потоке, они эффективно однопоточные, что означает, что только один поток выполняет код активации вашего зерна в любой конкретный момент времени. Наличие одного потока на активацию зерна было бы очень дорого и плохо масштабировалось.   -  person Reuben Bond    schedule 31.07.2018
comment
@ReubenBond @Tseng ... Думаю, я получил ответ на вторую часть своего вопроса. Внешнее зерно постоянно занимает поток выполнения и не дает возможности выполнить внутреннее зерно. Когда я жду внутри цикла while (или полностью удаляю цикл while, вызываю Task на внутреннем зерне и выполняю его без ожидания), поток становится доступным для внутреннего зерна для выполнения. Это правильно? @Tseng ... что ты имел в виду под Thread.Sleep(1000000000) is > then 00:30. Какое значение имеют 30-е годы?   -  person AvinashK    schedule 31.07.2018
comment
Игнорируйте это, перепутали его с кодом клиентской части в вашем примере   -  person Tseng    schedule 31.07.2018
comment
@ReubenBond .. Что касается первой части, мне пока непонятно. Итак, что происходит, когда зерно или клиент вызывает метод другого зерна? Кажется, что вызов не блокируется, поскольку клиент продвигается вперед даже до того, как задача запускается на целевом зерне. Итак, это похоже на то, что среда выполнения Орлеана ставит задачу в очередь для целевого зерна и возвращает ссылку на задачу вызывающей стороне, а вызывающая сторона блокируется до тех пор, пока не произойдет это двустороннее обращение? Таким образом, он будет отличаться от обычного C #, где вызывающий блокируется до тех пор, пока не встретится первое ожидание.   -  person AvinashK    schedule 31.07.2018
comment
@Tseng ... Теперь у меня есть вопрос о том, находится ли клиентский код в том же приложении / процессе, что и зерна ?. Да, клиент и хранилище запускаются одним и тем же процессом. Итак, среда выполнения Orleans создает всего 2 потока - один используется клиентом, а другой - зернами? И внешнее зерно забивает второй поток, не позволяя первому потоку выполняться. Можем ли мы указать количество потоков, которые среда выполнения Orleans может создать в рамках процесса, или количество потоков, которое Орлеан определяет динамически?   -  person AvinashK    schedule 31.07.2018
comment
Потоки управляются ThreadPool и внутренним планировщиком orleans. grains запускаются во внутреннем планировщике и обычно имеют небольшое количество потоков (CPU * 2 iirc, хотя можно настроить). ThreadPool обычно управляется средой выполнения. ожидание не означает запуск потока. он в основном используется для операций ввода-вывода (которые не требуют потока или процессора, поскольку они управляются через порты конкуренции ввода-вывода). Но переключение контекста происходит, и когда вы блокируете контекст во время выполнения асинхронного режима и хотите вернуться в этот контекст, вы можете попасть в тупик.   -  person Tseng    schedule 31.07.2018
comment
blog.stephencleary.com/2013/11/there-is- no-thread.html может быть полезным   -  person Tseng    schedule 31.07.2018
comment
@ReubenBond ... Простите меня, но я все еще не понимаю, есть ли запрос на блокировку другого зерна ?. В моем примере это не блокирует. Клиент продолжает выполнение, даже если выполнение зерна еще не началось. Ранее вы сказали нечто подобное о вызываемом интерфейсе, и вызов не блокировался. Однако мы возвращаем объект Task (статус WaitingForActivation) для каждого запроса (даже если мы не ожидаем). Итак, ставит ли среда выполнения Orleans в очередь задачу для нацеливания на зернистость и возвращает ссылку на задачу, а вызывающая сторона блокируется до тех пор, пока не произойдет это двустороннее обращение? Любые связанные блоги приветствуются.   -  person AvinashK    schedule 02.08.2018
comment
См. Также этот ответ: stackoverflow.com/questions/51664117/   -  person Reuben Bond    schedule 03.08.2018


Ответы (1)


Первая часть
Нет, при вызове зерна блокировки нет. Это сообщение дополнительно разъясняет, что происходит, когда зерновой вызов сделан.

Вторая часть
Хотя то, что зерна являются однопоточными, правильно, неверно предполагать, что каждое зерно имеет свою собственную резьбу в Орлеане. Как говорит @Tseng Orleans uses the async feature of .NET Core. It will process a grain until an async operation happens. Then it returns the thread to the thread-pool. This thread can be used by another grain to process data until the async operation is complete. When its complete, it resumes. its not necessary the same thread (but its the same context). Первое зерно блокирует поток, не давая возможности выполниться второму зерну.

Спасибо Ценгу и Рубену Бонду за то, что они прояснили ситуацию.

person AvinashK    schedule 03.08.2018