Uma biblioteca de simultaneidade e estadual para .NET brilha em chamadas de rede.
Pacote Nuget disponível em https://www.nuget.org/packages/serviceConcurrency/
Uma biblioteca para lidar com simultaneidade e estado, principalmente para código que chama os serviços da Web.
Primeiro, impede que os chamados desnecessários aconteçam. Quando uma chamada com argumentos correspondentes já está em voo, o chamador simultâneo está estacionado e será retomado quando a solicitação de origem terminar. Se o argumento é uma coleção, as entidades já em voo são despojadas.
Segundo, ele armazena em cache o estado de valor de retorno de valor. Quando existe um valor em cache para qualquer solicitação, ele será devolvido em vez de uma chamada sendo feita. Esse valor pode ser acessado a qualquer momento, impedindo a necessidade de campos de apoio adicionais em seu código. Se o argumento for uma coleção, as entidades em cache são retiradas do argumento.
Esta biblioteca usa o iMemoryCache para armazenamento interno de dados.
Aqui estão algumas coisas que o ServiceConcurrency pode lidar para você. Exemplos mais detalhados podem ser encontrados em seções posteriores deste documento.
GetSessionToken () retornará um valor em cache depois que uma primeira chamada foi feita. Se o getSessionToken () for chamado simultaneamente, embora ainda não tenha um valor em cache, apenas uma chamada para fetchSessionToken () será feita e as outras chamadas simultâneas renderão até que um valor esteja disponível.
private ServiceConcurrency . ReturnsValue < string > sessionTokenState =
new ServiceConcurrency . ReturnsValue < string > ( ) ;
public async Task < string > GetSessionToken ( )
{
return await this . sessionTokenState . Execute ( this . FetchSessionToken ) ;
}O mesmo que acima, mas a coleta de argumentos é despojada de valores que já estão em cache ou em vôo. Portanto, o FetchUserProfiles () nunca será chamado com um ID mais de uma vez.
private ServiceConcurrency . TakesEnumerationArgReturnsValue < Guid , UserProfile > userProfilesState =
new ServiceConcurrency . TakesEnumerationArgReturnsValue < Guid , UserProfile > ( ) ;
public async Task < IEnumerable < UserProfile > > GetUserProfiles ( IEnumerable < Guid > userProfileIds )
{
return await this . userProfilesState . Execute (
this . FetchUserProfiles ,
( guid , results ) => results . SingleOrDefault ( t => t . Id == guid ) ,
userProfileIds
) ;
} Existem quatro classes nesta biblioteca:
NOARGNOVALUE
Impede chamadas simultâneas, permitindo apenas uma chamada ativa por vez, onde as chamadas simultâneas aguardam o término da chamada ativa.
ReturnSValue <vvalue>
Apenas uma chamada será feita e as chamadas subsequentes buscarão o valor do cache. Também impede que as chamadas simultâneas ocorram, permitindo apenas uma chamada ativa por vez, onde as chamadas simultâneas aguardam o término da chamada ativa.
TakesArg <Targ>
Impede chamadas simultâneas ao compartilhar o mesmo argumento, permitindo apenas uma chamada ativa a um tempo por argumento, onde as chamadas simultâneas aguardam a chamada ativa para terminar.
TakesArGreTurnsValue <Targ, TValue>
Para um determinado argumento, apenas uma chamada será feita e as chamadas subsequentes buscarão o valor do cache. Também impede que as chamadas simultâneas ocorram, permitindo apenas uma chamada ativa a um tempo por argumento, onde as chamadas simultâneas aguardam a chamada ativa para terminar.
TakeenumerationArg <Targ>
As chamadas simultâneas serão executadas apenas uma vez para um determinado argumento na coleção de argumentos.
A coleção de argumentos é despojada dos valores para os quais uma operação já está em voo.
Então, simultaneamente, chamando com ["A", "B", "C"] e ["B", "C", "D"] resultará em uma chamada com ["A", "B", "C"] e um com ["d"].
TakeenumerationArgReturnsValue <Targ, TValue>
A primeira chamada e quaisquer chamadas simultâneas serão executadas apenas uma vez para um determinado argumento na coleção de argumentos. As chamadas subsequentes buscarão o valor do cache.
A coleta de argumentos é despojada dos valores para os quais existe um valor em cache ou uma operação já está em voo.
Então, simultaneamente, chamando com ["A", "B", "C"] e ["B", "C", "D"] resultará em uma chamada com ["A", "B", "C"] e um com ["d"]. Na próxima vez que "A", "B", "C" ou "D" for chamado com, ele será retirado da coleção e um valor para ela será buscado no cache.
Todos os objetos ServiceCoCurrency possuem um construtor sem parâmetros, caso em que um imemorycache interno será criado.
Todos os objetos ServiceCoCurrency que retornam os valores também possuem um construtor que aceita um objeto ImemoryCache, caso você queira compartilhar o cache com todos os objetos ServiceCoCurrency. Se o fizer, use chaves exclusivas nas entradas.
T Execute ( .. . )Executa uma solicitação específica. Você fornece um retorno de chamada para fazer a chamada externa. Consulte os exemplos para obter mais informações, pois os argumentos e tipos de retorno são específicos do tipo ServiceCoCurrency. O valor resultante é armazenado em cache e fornecido a você em seu retorno de chamada.
void Reset ( )Redefina o estado interno, ou seja, o estado para chamadas em processo e cache interno (caso o objeto ServiceCoCurrency retorne valores).
bool IsExecuting ( )Retorna se o objeto ServiceCoCurrency está atualmente executando uma solicitação específica.
Os seguintes métodos estão disponíveis apenas em objetos ServiceCoCurrency que retornam valores.
void ResetCache ( )Redefina o cache interno. Também chamado de Reset ().
bool TryGetValue ( TArg key , out TValue value )Recebe um valor do cache.
void Set ( TArg key , TValue value )Um conjunto de cache no caso raro que você pode precisar manipular manualmente o cache.
void Remove ( TArg key )Remove um valor do cache.
bool ContainsKey ( TArg key )Verifica se existe uma entrada no cache.
TValue this [ TArg key ]Operador de matriz para definir e obter entradas de cache. Lança uma KeyNotFoundException no getter se uma entrada não existir.
IEnumerator < KeyValuePair < TArg , TValue > > GetEnumerator ( )
IEnumerator IEnumerable . GetEnumerator ( )Enumeradores para o cache interno. Permite usar os objetos nas instruções FOREACH e LINQ.
void Dispose ( ) Dispa do cache interno, caso Bool IsCacheShared seja falso. Se for um cache compartilhado, ele chamará ResetCache .
TValue Value ;Somente no retorno Value <vvalue>. Este é o objeto em cache único.
As propriedades a seguir estão disponíveis apenas em objetos ServiceCoCurrency que retornam valores.
MemoryCacheEntryOptions CacheEntryOptions ;Essas opções são usadas internamente quando um valor é armazenado em cache. Editando isso permite que você defina expiração, tamanhos de cache etc. Consulte MemoryCacheOptions para obter mais detalhes.
bool IsCacheShared ;Apenas getter. Indica se o cache for compartilhado ou não.
Execute () aceita um conversor de valor opcional, que pode modificar o valor buscado antes de retornar e armazená -lo em cache. Isso está disponível apenas nos objetos ServiceCoCurrency que retornam valores.
private ServiceConcurrency . ReturnsValue < string > lastName =
new ServiceConcurrency . ReturnsValue < string > ( ) ;
private const string FirstName = "John" ;
public async Task < string > GetFullName ( )
{
return await this . lastName . Execute (
this . GetLastName ,
( lastName ) => $ " { FirstName } { lastName } " ;
) ;
}Os objetos ServiceCoCurrency também aceitam um parâmetro adicional que declare o tipo de valor da solicitação interna. Quando isso é especificado, o conversor de valor também converterá entre o tipo de origem e o tipo de destino. Isso é útil se a solicitação invocada pelo Execute () for de um tipo diferente do campo de apoio desejado.
// FetchChatRooms() returns an IEnumerable<ChatRoom>, chatRoomMap handles it as Dictionary<Guid, ChatRoom>
private ServiceConcurrency . ReturnsValue < IEnumerable < ChatRoom > , Dictionary < Guid , ChatRoom > > chatRoomMap =
new ServiceConcurrency . ReturnsValue < IEnumerable < ChatRoom > , Dictionary < Guid , ChatRoom > > ( ) ;
public async Task < IEnumerable < ChatRoom > > UpdateChatRooms ( )
{
return ( await this . chatRoomMap . Execute (
this . FetchChatRooms ,
( chatRooms ) => chatRooms . ToDictionary ( t => t . Id , t => t ) // cache as id -> chatroom map
) ) ? . Values ;
}
public ChatRoom GetChatRoom ( Guid chatRoomId )
{
ChatRoom chatRoom ;
if ( this . chatRoomMap . Value . TryGetValue ( chatRoomId , out chatRoom ) ) // value is Dictionary<Guid, ChatRoom>
return chatRoom ;
return null ;
} using System ;
using System . Collections . Generic ;
using System . Threading . Tasks ;
using System . Linq ;
public class MyService : IDisposable
{
////////////////////////////////////////////////////////////////////////////
// NoArgNoValue example
private ServiceConcurrency . NoArgNoValue simpleCallState =
new ServiceConcurrency . NoArgNoValue ( ) ;
// Concurrent calls won't invoke the callback multiple times - only the first
// call will invoke it, and the rest will wait until it finishes.
public async Task CallSomething ( )
{
await this . simpleCallState . Execute (
async ( ) =>
{
Console . WriteLine ( "CallSomething call in flight" ) ;
await Task . Delay ( 100 ) ;
}
) ;
}
////////////////////////////////////////////////////////////////////////////
// ReturnsValue example
private ServiceConcurrency . ReturnsValue < string > returnsValueState =
new ServiceConcurrency . ReturnsValue < string > ( ) ;
// Only one call will be made and subsequent calls will fetch the value from
// cache. Also prevents any concurrent calls from occurring, by allowing only
// one active call at a time, where concurrent calls will wait for the active
// call to finish.
public async Task < string > FetchSomething ( )
{
return await this . returnsValueState . Execute (
async ( ) =>
{
Console . WriteLine ( "FetchSomething call in flight" ) ;
await Task . Delay ( 100 ) ;
return "Hello world!" ;
}
) ;
}
////////////////////////////////////////////////////////////////////////////
// TakesArg example
private ServiceConcurrency . TakesArg < Guid > takesArgState =
new ServiceConcurrency . TakesArg < Guid > ( ) ;
// Prevents concurrent calls when sharing the same argument, by allowing only
// one active call at a time per argument, where concurrent calls will wait for
// the active call to finish.
public async Task PostSomething ( Guid someId )
{
await this . takesArgState . Execute (
async ( Guid id ) =>
{
Console . WriteLine ( $ "PostSomething call in flight, for argument { id } " ) ;
await Task . Delay ( 100 ) ;
} ,
someId
) ;
}
////////////////////////////////////////////////////////////////////////////
// TakesArgReturnsValue example
private ServiceConcurrency . TakesArgReturnsValue < Guid , string > takesArgReturnsValueState =
new ServiceConcurrency . TakesArgReturnsValue < Guid , string > ( ) ;
// For a given argument, only one call will be made and subsequent calls will
// fetch the value from cache. Also prevents any concurrent calls from occurring,
// by allowing only one active call at a time per argument, where concurrent
// calls will wait for the active call to finish.
public async Task < string > FetchSomethingFor ( Guid someId )
{
return await this . takesArgReturnsValueState . Execute (
async ( Guid id ) =>
{
Console . WriteLine ( $ "FetchSomethingFor call in flight, for argument { id } " ) ;
await Task . Delay ( 100 ) ;
return $ "The guid is { id } " ;
} ,
someId
) ;
}
////////////////////////////////////////////////////////////////////////////
// TakesEnumerationArg example
private ServiceConcurrency . TakesEnumerationArg < Guid > takesEnumerationArgState =
new ServiceConcurrency . TakesEnumerationArg < Guid > ( ) ;
// Concurrent calls will execute only once for a given argument in the argument
// collection.
//
// The argument collection is stripped of values for which an operation is already
// in flight.
//
// So simultaneously calling with ["A", "B", "C"] and ["B", "C", "D"] will result
// in one call with ["A", "B", "C"] and one with ["D"].
public async Task PostCollection ( IEnumerable < Guid > someIds )
{
await this . takesEnumerationArgState . Execute (
async ( IEnumerable < Guid > ids ) =>
{
Console . WriteLine ( $ "PostCollection call in flight, for arguments { ids . Select ( t => t ) } " ) ;
await Task . Delay ( 100 ) ;
} ,
someIds
) ;
}
////////////////////////////////////////////////////////////////////////////
// TakesEnumerationArgReturnsValue example
private ServiceConcurrency . TakesEnumerationArgReturnsValue < Guid , ExampleClass > takesEnumArgReturnsValueState =
new ServiceConcurrency . TakesEnumerationArgReturnsValue < Guid , ExampleClass > ( ) ;
public class ExampleClass
{
public Guid Id { get ; set ; }
}
// The first call, and any concurrent calls, will execute only once for a
// given argument in the argument collection. Subsequent calls will fetch value
// from cache.
//
// The argument collection is stripped of values for which a cached value exists
// or an operation is alraedy in flight.
//
// So simultaneously calling with ["A", "B", "C"] and ["B", "C", "D"] will
// result in one call with ["A", "B", "C"] and one with ["D"].
// The next time "A", "B", "C" or "D" is called with, it will be stripped from
// the collection and a value for it will be fetched from the cache.
public async Task < IEnumerable < ExampleClass > > FetchCollectionForThese ( IEnumerable < Guid > someIds )
{
return await this . takesEnumArgReturnsValueState . Execute (
async ( IEnumerable < Guid > ids ) =>
{
Console . WriteLine ( $ "FetchCollectionForThese call in flight, for arguments { ids . Select ( t => t ) } " ) ;
await Task . Delay ( 100 ) ;
return ids . Select ( t => new ExampleClass ( )
{
Id = t
} ) ;
} ,
// a mapper from arg to result is required - should return the corresponding value for the passed argument
( Guid id , IEnumerable < ExampleClass > result ) => result . SingleOrDefault ( t => t . Id == id ) ,
someIds
) ;
}
void Dispose ( )
{
this . simpleCallState . Dispose ( ) ;
this . returnsValueState . Dispose ( ) ;
this . takesArgState . Dispose ( ) ;
this . takesArgReturnsValueState . Dispose ( ) ;
this . takesEnumerationArgState . Dispose ( ) ;
this . takesEnumArgReturnsValueState . Dispose ( ) ;
}
}