Visão geral do agendamento
Existem duas formas de agendamento em Orleans que são relevantes para os grãos:
- Agendamento de solicitações, o agendamento de chamadas de grãos de entrada para execução de acordo com as regras de agendamento discutidas em Agendamento de solicitação.
- Agendamento de tarefas, o agendamento de blocos síncronos de código a serem executados de forma de thread único
Todo o código de grão é executado no agendador de tarefas do grão, o que significa que as solicitações também são executadas no agendador de tarefas do grão. Mesmo que as regras de agendamento de solicitações permitam que várias solicitações sejam executadas simultaneamente, elas não serão executadas em paralelo porque o agendador de tarefas do grão sempre executa tarefas uma a uma e, portanto, nunca executa várias tarefas em paralelo.
Agendamento de tarefas
Para entender melhor o agendamento, considere o seguinte grão MyGrain
, que tem um método chamado DelayExecution()
que registra uma mensagem, espera algum tempo e, em seguida, 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 é executado, o corpo do método será executado em duas partes:
- A primeira
_logger.LogInformation(...)
chamada e a chamada paraTask.Delay(1_000)
. - A segunda
_logger.LogInformation(...)
chamada.
A segunda tarefa não será agendada no agendador de tarefas do grão até que a Task.Delay(1_000)
chamada seja concluída, momento em que ele agendará a continuação do método de grão.
Aqui está uma representação gráfica de como uma solicitação é agendada e executada como duas tarefas:
A descrição acima não é específica e Orleans é como o agendamento de tarefas no .NET funciona: métodos assíncronos em C# são convertidos em uma máquina de estado assíncrona pelo compilador e a execução progride através da máquina de estado assíncrona em etapas discretas. Cada etapa é agendada no atual TaskScheduler (acessado via TaskScheduler.Current, padrão para TaskScheduler.Default) ou no atual SynchronizationContext. Se um TaskScheduler
está sendo usado, cada etapa do método é representada por uma Task
instância que é passada para esse TaskScheduler
. Portanto, um Task
em .NET pode representar duas coisas conceituais:
- Uma operação assíncrona que pode ser esperada. A execução do
DelayExecution()
método acima é representada por umTask
que pode ser aguardado. - Em um bloco de trabalho síncrono, cada estágio dentro do
DelayExecution()
método acima é representado por umTask
.
Quando TaskScheduler.Default
está em uso, as continuações são agendadas diretamente no .NET ThreadPool e não são encapsuladas em um Task
objeto. O encapsulamento de continuações em Task
instâncias ocorre de forma transparente e, portanto, os desenvolvedores raramente precisam estar cientes desses detalhes de implementação.
Agendamento de tarefas em Orleans
Cada ativação de grão tem sua própria TaskScheduler
instância que é responsável por aplicar o modelo de execução de thread único de grãos. Internamente, isso TaskScheduler
é implementado via ActivationTaskScheduler
e WorkItemGroup
. WorkItemGroup
mantém tarefas enfileiradas em um Queue<T> onde é um Task
internamente e implementa IThreadPoolWorkItemT
. Para executar cada um atualmente enfileiradoTask
, agenda-se no .NET ThreadPool
WorkItemGroup
. Quando o .NET ThreadPool
invoca o WorkItemGroup
método 's, o WorkItemGroup
executa as instâncias enfileiradas Task
IThreadPoolWorkItem.Execute()
uma a uma.
Cada grão 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 a ele. Isso inclui agendadores de grãos , bem como outros itens de trabalho, como itens de trabalho agendados via Task.Run(...)
:
Nota
O agendador de um grão só pode ser executado em um thread de cada vez, mas nem sempre é executado no mesmo thread. O .NET ThreadPool
é livre para usar um thread diferente cada vez que o agendador do grão é executado. O agendador de grãos é responsável por garantir que ele seja executado apenas em um thread de cada vez e é assim que o modelo de execução de grãos de thread único é implementado.