Visão geral do agendamento
Há duas formas de agendamento no Orleans que são relevantes para a granularidade:
- Agendamento de solicitação, o agendamento de chamadas de granularidade de entrada para execução de acordo com as regras de agendamento discutidas no agendamento da Solicitação.
- Agendamento de tarefas, o agendamento de blocos síncronos de código a serem executados em uma maneira de thread única
Todo o código de granularidade é executado no agendador de tarefas de granularidade, o que significa que as solicitações também são executadas no agendador de tarefas da granularidade. Mesmo que as regras de agendamento de solicitação permitam que várias solicitações sejam executadas simultaneamente, elas não serão executadas em paralelo porque o agendador de tarefas de granularidade sempre executa tarefas um por um e, portanto, nunca executa várias tarefas em paralelo.
Agendamento de tarefas
Para reconhecer melhor o agendamento, considere a seguinte granularidade, MyGrain
, que tem um método chamado DelayExecution()
que registra uma mensagem, aguarda algum tempo e registra outra mensagem antes de retornar.
public interface IMyGrain : IGrain
{
Task DelayExecution();
}
public class MyGrain : Grain, IMyGrain
{
private readonly ILogger<MyGrain> _logger;
public MyGrain(ILogger<MyGrain> logger) => _logger = logger;
public async Task DelayExecution()
{
_logger.LogInformation("Executing first task");
await Task.Delay(1_000);
_logger.LogInformation("Executing second task");
}
}
Quando esse método for executado, o corpo do método será executado em duas partes:
- A primeira chamada
_logger.LogInformation(...)
e a chamada paraTask.Delay(1_000)
. - A segunda chamada
_logger.LogInformation(...)
.
A segunda tarefa não será agendada no agendador de tarefas de granularidade até que a chamada Task.Delay(1_000)
seja concluída, momento em que agendará a continuação do método de granularidade.
Aqui temos uma representação gráfica de como uma solicitação é agendada e executada como duas tarefas:
A descrição acima não é específica do Orleans e é como o agendamento de tarefas no .NET funciona: métodos assíncronos em C# são convertidos em uma máquina de estado assíncrono pelo compilador, e a execução progride pela máquina de estado assíncrono em etapas discretas. Cada etapa é agendada no TaskScheduler atual (acessado por meio TaskScheduler.Current, padronizando para TaskScheduler.Default) ou SynchronizationContext atual. Se um TaskScheduler
estiver sendo usado, cada etapa no método será representada por uma instância Task
que é passada para aquele TaskScheduler
. Portanto, um Task
no .NET pode representar duas coisas conceituais:
- Uma operação assíncrona que pode ser aguardada. A execução do método
DelayExecution()
acima é representada por umTask
que pode ser aguardado. - Em um bloco síncrono de trabalho, cada estágio dentro do método
DelayExecution()
acima é representado por umTask
.
Quando TaskScheduler.Default
estiver em uso, as continuações são agendadas diretamente no .NET ThreadPool e não são encapsuladas em um objeto Task
. O encapsulamento de continuações em instâncias Task
ocorre de forma transparente e, portanto, os desenvolvedores raramente precisam estar cientes desses detalhes de implementação.
Agendamento de tarefas no Orleans
Cada ativação de granularidade tem sua própria instância TaskScheduler
, que é responsável por impor o modelo de execução de granularidade de thread único. Internamente, este TaskScheduler
é implementado por meio de ActivationTaskScheduler
e WorkItemGroup
. WorkItemGroup
mantém tarefas enfileiradas em um Queue<T> onde T
é um Task
internamente e implementa IThreadPoolWorkItem. Para executar cada Task
atualmente enfileirado, WorkItemGroup
agenda a si mesmo no .NET ThreadPool
. Quando o .NET ThreadPool
invoca o método IThreadPoolWorkItem.Execute()
de WorkItemGroup
, o WorkItemGroup
executa as instâncias Task
enfileiradas um por um.
Cada granularidade tem um agendador que é executado agendando-se no .NET ThreadPool
:
Cada agendador contém uma fila de tarefas:
O .NET ThreadPool
executa cada item de trabalho enfileirado nele. Isso inclui agendadores de granularidade, bem como outros itens de trabalho, como itens de trabalho agendados por meio de Task.Run(...)
:
Observação
O agendador de uma granularidade só pode ser executado em um thread por vez, mas nem sempre é executado no mesmo thread. O .NET ThreadPool
é livre para usar um thread diferente sempre que o agendador do granularidade for executado. O agendador de granularidade é responsável por garantir que ele seja executado apenas em um thread por vez e é assim que o modelo de execução de thread único de granularidades é implementado.