Apresentando o ServiceActor: biblioteca .NET livre de bloqueio multi-threading baseada no modelo Actor

Adolfo Marinucci em codeburst Segue 5 de jul · 3 min ler Foto por Will Truettner em Unsplash

O bloqueio de recursos é a solução padrão em c # para lidar com problemas causados por multi-threading. ServiceActor é uma biblioteca que desenvolvi e integrei com sucesso em um grande projeto para lidar com problemas de simultaneidade causados pelo uso extensivo de bloqueios.

Tomemos por exemplo este serviço ICounter simples:

 interface pública ICounter 
{
int Contagem {get; }
Incremento void ();
}

e esta classe de implementação:

 Contador de classe privada: ICounter 
{
public int Contagem {get; conjunto privado; }
Incremento de vazio público ()
{
Contagem + = 1;
}
}

Como o método Increment não cria nenhum bloqueio para o estado interno Count , ele não é intrinsecamente thread-safe:

 Contador ICounter = novo Contador (); // ou obter do DI 
Task.WaitAll (
Enumerável
.Range (0, 10)
.Selecione (_ =>
Task.Factory.StartNew (() =>
{
counter.Increment ();
}))
.ToArray ());
Assert.AreEqual (10, counter.Count); // <--- isso irá falhar !!!

Como disse a maneira usual para resolver problemas de multi-threading é para bloquear um recurso:

 Contador de classe privada: ICounter 
{
public int Contagem {get; conjunto privado; }
Incremento de vazio público ()
{
bloquear (isso)
Contagem + = 1;
}
}

Não vou discutir aqui porque bloquear recursos é perigoso, especialmente em grandes projetos com código escrito por programadores novatos, nem estou dizendo que essa abordagem não funciona perfeitamente em muitos projetos de qualidade de produção. O ServiceActor deve ser considerado como uma ferramenta adicional para lidar com esses problemas de concorrência.

Como funciona o ServiceActor

O ServiceActor dinamicamente (on-the-fly) cria uma classe de proxy para o serviço para que qualquer chamada para métodos ou propriedades seja enfileirada. O ServiceActor garante que apenas um thread pode acessar o serviço por vez.

Em relação ao exemplo acima, mudaria como:

 ... 
Incremento de vazio público ()
{
Contagem + = 1;
}
...
var counter = ServiceRef.Create <ICounter> (novo contador () / * ou get de DI * /);
...
Assert.AreEqual (10, counter.Count); // - isso agora é bem sucedido!

Como você percebeu, a implementação original não foi modificada.

Nos bastidores, o ServiceActor cria uma fila de System.Action para cada serviço. Cada ação é executada sobre a classe de serviço original.

Wrapper ServiceActor com fila de ações

Por padrão, invocação de métodos sem tipos de retorno (isto é, void) e setters de propriedades não estão bloqueando , enquanto chamadas para métodos com tipos de retorno (incluindo também Task) e getters de propriedades estão bloqueando . Isso é importante porque permite que o código do cliente não pare durante a chamada dos serviços. Estes, como outros aspectos do ServiceActor, podem ser customizados e, para isso, eu o direcionarei para o repositório do GitHub.

Outro ótimo recurso do ServiceActor é a capacidade de coordenar o acesso a mais de um serviço (usando o atributo ServiceDomain). Isso é particularmente útil quando um agregado de serviços deve trabalhar em conjunto para realizar uma tarefa.

O ServiceActor funciona muito bem com as chamadas async / await, restaurando o contexto de sincronização entre as chamadas assíncronas.

 tarefa assíncrona pública IncrementAsync () 
{
int threadId = Thread.CurrentThread.ManagedThreadId;
aguardar Task.Delay (100);
Contagem + = 1;
Assert.AreEqual (threadId, Thread.CurrentThread.ManagedThreadId);
}

Principais objetivos do ServiceActor

  1. Fornecer uma solução fácil e direta para problemas de multithreading sem exigir bloqueios em recursos e / ou re-design completo de serviços.
  2. Deve ser plugable: em outras palavras, deve ser possível integrá-lo em projetos existentes também com apenas uma parte específica deles.
  3. Deve ser assíncrono / aguardar amigável
  4. Deve ser diretamente utilizável em estruturas de DI
  5. Ele deve suportar o tratamento de exceções nativas com estratégias de recuperação de serviço personalizadas

O que o ServiceActor não é

O ServiceActor não tem como objetivo ser um framework de golpe completo, nem uma implementação completa do modelo Actor, na verdade, nem mesmo é possível realizar uma tarefa tão grande para um único desenvolvedor. Então, se você está interessado em uma implementação real do modelo Actor, por favor, dê uma olhada em Akka.NET ou Orleans.

Código fonte:
https://github.com/adospace/ServiceActor