Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Por design, todas as subtarefas geradas a partir do código grain (por exemplo, usando await
, ContinueWith
ou Task.Factory.StartNew
) são despachadas na mesma ativação TaskScheduler que a tarefa pai. Portanto, eles herdam o mesmo modelo de execução de thread único que o resto do código de grão. Este é o principal ponto por trás da execução single-threaded da simultaneidade baseada em turnos de grãos.
Em alguns casos, o código "grain" pode precisar "sair" do modelo de agendamento de tarefas e "fazer algo especial", como apontar explicitamente um Orleans para um agendador de tarefas diferente ou para o .NET Task
. Um exemplo é quando o código grain precisa executar uma chamada de bloqueio remoto síncrona (como E/S remota). Executar essa chamada de bloqueio no contexto de grão bloqueia o grão e, portanto, nunca deve ser feito. Em vez disso, o código grain pode executar essa parte do código de bloqueio num thread de um pool de threads, aguardar (await
) a conclusão dessa execução e, em seguida, prosseguir no contexto do grain. Espera-se que escapar do agendador seja um cenário de uso muito avançado e raramente necessário, além dos Orleans padrões de uso típicos.
APIs baseadas em tarefas
await
, TaskFactory.StartNew (veja abaixo), Task.ContinueWith, , Task.WhenAnyTask.WhenAll, e Task.Delay todos respeitam o agendador de tarefas atual. Isso significa usá-los da maneira padrão, sem passar um TaskScheduler diferente, resulta na sua execução no contexto granular.Ambos Task.Run e o
endMethod
delegado de não TaskFactory.FromAsync respeitam o agendador de tarefas atual. Ambos usam oTaskScheduler.Default
agendador, agendador de tarefas do pool de threads do .NET. Portanto, o código dentroTask.Run
e oendMethod
inTask.Factory.FromAsync
sempre são executados no pool de threads do .NET, fora do modelo de execução de thread único para Orleans grãos. No entanto, qualquer código depoisawait Task.Run
ouawait Task.Factory.FromAsync
é executado novamente sob o agendador ativo no ponto em que a tarefa foi criada, que é o agendador associado ao grão.Task.ConfigureAwait with
false
é uma API explícita para escapar do agendador de tarefas atual. Faz com que o código após um aguardadoTask
seja executado no agendador TaskScheduler.Default (o pool de threads .NET), quebrando assim a execução single-threaded da tarefa.Atenção
Geralmente, nunca use
ConfigureAwait(false)
diretamente no código granular.Métodos com a assinatura
async void
não devem ser usados com grãos. Eles são destinados a manipuladores de eventos de interface gráfica do usuário. Umasync void
método pode travar imediatamente o processo atual se permitir que uma exceção escape, sem nenhuma maneira de lidar com a exceção. Isso também se aplica aList<T>.ForEach(async element => ...)
e a qualquer outro método que aceite um Action<T>, uma vez que o delegado assíncrono se converte em umasync void
delegado.
Task.Factory.StartNew
e async
delegados
A recomendação usual para agendar tarefas em C# é usar Task.Run
em vez de Task.Factory.StartNew
. Uma rápida pesquisa na web sugere Task.Factory.StartNew
que é perigoso e favorecimento Task.Run
é sempre recomendado. No entanto, para ficar dentro do modelo de execução single-threaded do grão, Task.Factory.StartNew
deve ser usado. Então, como usá-lo corretamente? O perigo com Task.Factory.StartNew()
é a sua falta de suporte nativo para delegados assíncronos. Isso significa que código como var notIntendedTask = Task.Factory.StartNew(SomeDelegateAsync)
é provavelmente um bug.
notIntendedTask
não é uma tarefa concluída quando SomeDelegateAsync
termina. Em vez disso, sempre desembrulhe a tarefa retornada: var task = Task.Factory.StartNew(SomeDelegateAsync).Unwrap()
.
Exemplo: Várias tarefas e o agendador de tarefas
Abaixo está o código de exemplo demonstrando o uso de TaskScheduler.Current
, Task.Run
, e um agendador personalizado especial para escapar do contexto do Orleans grão e como retornar a ele.
public async Task MyGrainMethod()
{
// Grab the grain's task scheduler
var orleansTS = TaskScheduler.Current;
await Task.Delay(10_000);
// Current task scheduler did not change, the code after await is still running
// in the same task scheduler.
Assert.AreEqual(orleansTS, TaskScheduler.Current);
Task t1 = Task.Run(() =>
{
// This code runs on the thread pool scheduler, not on Orleans task scheduler
Assert.AreNotEqual(orleansTS, TaskScheduler.Current);
Assert.AreEqual(TaskScheduler.Default, TaskScheduler.Current);
});
await t1;
// We are back to the Orleans task scheduler.
// Since await was executed in Orleans task scheduler context, we are now back
// to that context.
Assert.AreEqual(orleansTS, TaskScheduler.Current);
// Example of using Task.Factory.StartNew with a custom scheduler to escape from
// the Orleans scheduler
Task t2 = Task.Factory.StartNew(() =>
{
// This code runs on the MyCustomSchedulerThatIWroteMyself scheduler, not on
// the Orleans task scheduler
Assert.AreNotEqual(orleansTS, TaskScheduler.Current);
Assert.AreEqual(MyCustomSchedulerThatIWroteMyself, TaskScheduler.Current);
},
CancellationToken.None,
TaskCreationOptions.None,
scheduler: MyCustomSchedulerThatIWroteMyself);
await t2;
// We are back to Orleans task scheduler.
Assert.AreEqual(orleansTS, TaskScheduler.Current);
}
Exemplo: Fazer uma chamada para um grain a partir de código em execução numa thread do pool de threads
Outro cenário envolve o código de grain precisando "quebrar" o modelo de agendamento de tarefas do grain e ser executado numa thread de um pool de threads (ou algum outro contexto fora dos grains), mas ainda precisando chamar outro grain. As chamadas de grãos podem ser feitas a partir de contextos sem grãos, sem cerimônia extra.
O código a seguir demonstra como realizar uma chamada de grão a partir de código que está em execução no interior de um grão, mas fora do contexto desse grão.
public async Task MyGrainMethod()
{
// Grab the Orleans task scheduler
var orleansTS = TaskScheduler.Current;
var fooGrain = this.GrainFactory.GetGrain<IFooGrain>(0);
Task<int> t1 = Task.Run(async () =>
{
// This code runs on the thread pool scheduler,
// not on Orleans task scheduler
Assert.AreNotEqual(orleansTS, TaskScheduler.Current);
int res = await fooGrain.MakeGrainCall();
// This code continues on the thread pool scheduler,
// not on the Orleans task scheduler
Assert.AreNotEqual(orleansTS, TaskScheduler.Current);
return res;
});
int result = await t1;
// We are back to the Orleans task scheduler.
// Since await was executed in the Orleans task scheduler context,
// we are now back to that context.
Assert.AreEqual(orleansTS, TaskScheduler.Current);
}
Trabalhar com bibliotecas
Algumas bibliotecas externas usadas pelo código podem usar ConfigureAwait(false)
internamente. Usar ConfigureAwait(false)
é uma prática boa e correta no .NET ao implementar bibliotecas de uso geral. Isso não é um problema no Orleans. Contanto que o código grain invocando o método library aguarde a chamada da biblioteca com um regular await
, o código grain está correto. O resultado é exatamente como desejado: o código da biblioteca executa continuações no agendador padrão (o valor retornado pelo TaskScheduler.Default
, que não garante que as continuações sejam executadas em um thread ThreadPool, uma vez que frequentemente são integradas no thread anterior), enquanto o código de grão é executado no agendador do grão.
Outra pergunta freqüente é se as chamadas de biblioteca precisam ser executadas com Task.Run
—ou seja, se o código da biblioteca precisa de descarregamento explícito para o ThreadPool
(por exemplo, await Task.Run(() => myLibrary.FooAsync())
). A resposta é não. O descarregamento do código para o ThreadPool
não é necessário, exceto quando o código da biblioteca faz chamadas síncronas que bloqueiam. Normalmente, qualquer biblioteca assíncrona .NET bem escrita e correta (métodos retornando Task
e nomeados com um Async
sufixo) não faz chamadas de bloqueio. Assim, descarregar qualquer coisa para o ThreadPool
não é necessário, a menos que a biblioteca assíncrona seja suspeita de ser problemática ou que se utilize deliberadamente uma biblioteca de bloqueio síncrona.
Impasses
Como os grãos executam threads únicos, o bloqueio de um grão é possível bloqueando de forma síncrona de uma forma que exija o desbloqueio de vários threads. Isso significa que o código que chama qualquer um dos seguintes métodos e propriedades pode causar uma situação de deadlock em um grão se as tarefas fornecidas não tiverem sido concluídas até ao momento em que o método ou propriedade seja invocado.
Task.Wait()
Task.Result
Task.WaitAny(...)
Task.WaitAll(...)
task.GetAwaiter().GetResult()
Evite esses métodos em qualquer serviço de alta simultaneidade, pois eles podem levar a um desempenho ruim e instabilidade. Eles privam o .NET ThreadPool
bloqueando threads que poderiam executar trabalho útil e exigem a ThreadPool
injeção de threads adicionais para conclusão. Ao executar o código de grão, esses métodos podem causar o bloqueio do grão, portanto, evite-os no código de grão também.
Se algum trabalho de sincronização sobre assíncrono for inevitável, mover esse trabalho para um agendador separado é melhor. A maneira mais simples é usar await Task.Run(() => task.Wait())
, por exemplo. Tenha em mente que evitar tarefas de sincronização sobre assíncrono é fortemente recomendado, pois prejudica a escalabilidade e o desempenho da aplicação.
Resumo: Trabalhando com tarefas em Orleans
O que está a tentar fazer? | Como fazê-lo |
---|---|
Execute o trabalho em segundo plano em threads do pool de threads do .NET. Nenhum código de grão ou chamadas de grãos são permitidos. | Task.Run |
Execute tarefas de trabalho assíncronas a partir do código grain com Orleans garantias de simultaneidade baseadas em turnos (veja acima). |
Task.Factory.StartNew(WorkerAsync).Unwrap() (Unwrap) |
Execute tarefas de trabalho síncronas a partir do código grain com Orleans garantias de simultaneidade baseadas em turnos. | Task.Factory.StartNew(WorkerSync) |
Tempos limite para execução de itens de trabalho | Task.Delay + Task.WhenAny |
Chamar um método de biblioteca assíncrona |
await a chamada da biblioteca |
Utilizar o comando async /await |
O modelo de programação .NET Task-Async normal. Suportado & recomendado |
ConfigureAwait(false) |
Não use dentro do código de grão. Permitido apenas dentro de bibliotecas. |