Una concurrencia y biblioteca de estado para .NET, brilla en las llamadas de red.
Paquete Nuget disponible en https://www.nuget.org/packages/serviceconcurrency/
Una biblioteca para manejar la concurrencia y el estado, principalmente para el código que llama a los servicios web.
Primero, evita que ocurran llamadas innecesarias. Cuando una llamada con argumentos coincidentes ya está en vuelo, la persona que llama concurrente está estacionada y se reanudará cuando termine la solicitud de origen. Si el argumento es una colección, las entidades que ya están en vuelo están despojadas.
En segundo lugar, almacena en caché el estado de las solicitudes de devolución de valor. Cuando existe un valor en caché para cualquier solicitud dada, se devolverá en lugar de que se realice una llamada. Se puede acceder a este valor en cualquier momento, evitando la necesidad de campos de respaldo adicionales en su código. Si el argumento es una colección, las entidades almacenadas en caché se despojan del argumento.
Esta biblioteca utiliza ImemoryCache para almacenamiento en caché interno de datos.
Aquí hay algunas cosas que ServiceConcurrency puede manejar para usted. Se pueden encontrar ejemplos más profundos en secciones posteriores de este documento.
GetSessionToken () devolverá un valor en caché después de que se haya realizado una primera llamada. Si GetSessionToken () se llama simultáneamente mientras aún no tiene un valor en caché, solo se realizará una llamada a FetchSessionToken () y las otras llamadas concurrentes producirán hasta que un valor esté disponible.
private ServiceConcurrency . ReturnsValue < string > sessionTokenState =
new ServiceConcurrency . ReturnsValue < string > ( ) ;
public async Task < string > GetSessionToken ( )
{
return await this . sessionTokenState . Execute ( this . FetchSessionToken ) ;
}Igual que el anterior, pero la colección de argumentos está despojada de valores que ya están almacenados en caché o en vuelo. Entonces FetchuserProfiles () nunca se llamará con una identificación más de una 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
) ;
} Hay cuatro clases en esta biblioteca:
Noargnovalue
Previene las llamadas concurrentes al permitir solo una llamada activa a la vez, donde las llamadas concurrentes esperarán a que la llamada activa finalice.
DefurnsValue <value>
Solo se realizará una llamada y las llamadas posteriores obtendrán el valor de Cache. También evita que ocurra cualquier llamada concurrente, permitiendo solo una llamada activa a la vez, donde las llamadas concurrentes esperarán a que la llamada activa finalice.
Takarg <arg>
Previene las llamadas concurrentes al compartir el mismo argumento, permitiendo solo una llamada activa a la vez por argumento, donde las llamadas concurrentes esperarán a que la llamada activa finalice.
TakeArgreturnsValue <Targ, Tvalue>
Para un argumento determinado, solo se realizará una llamada y las llamadas posteriores obtendrán el valor de la caché. También evita que ocurran cualquier llamada concurrente, permitiendo solo una llamada activa a la vez por argumento, donde las llamadas concurrentes esperarán a que finalice la llamada activa.
Takesenumerationarg <arg>
Las llamadas concurrentes se ejecutarán solo una vez para un argumento determinado en la recopilación de argumentos.
La colección de argumentos se desprende de valores para los cuales una operación ya está en vuelo.
Entonces, llamar simultáneamente con ["A", "B", "C"] y ["B", "C", "D"] dará como resultado una llamada con ["A", "B", "C"] y otra con ["D"].
TakeEnumerationarGreturnsValue <Targ, Tvalue>
La primera llamada, y cualquier llamada concurrente, se ejecutará solo una vez para un argumento determinado en la recopilación de argumentos. Las llamadas posteriores obtendrán valor de caché.
La colección de argumentos se desprende de valores para los cuales existe un valor en caché o una operación ya está en vuelo.
Entonces, llamar simultáneamente con ["A", "B", "C"] y ["B", "C", "D"] dará como resultado una llamada con ["A", "B", "C"] y otra con ["D"]. La próxima vez que se llame "A", "B", "C" o "D", se eliminará de la colección y se obtendrá un valor para el caché.
Todos los objetos ServiceConcurrence tienen un constructor sin parámetros, en cuyo caso se creará un IMemoryCache interno.
Todos los objetos ServiceConcurrence que devuelven los valores también tienen un constructor que acepta un objeto ImemoryCache, en caso de que desee compartir el caché con todos los objetos ServiceConConcurrence. Si lo hace, asegúrese de usar claves únicas en las entradas.
T Execute ( .. . )Ejecuta una solicitud específica. Usted proporciona una devolución de llamada para hacer la llamada exterior. Consulte los ejemplos para obtener más información, ya que los argumentos y los tipos de devolución son específicos del tipo de subconocencia de servicios. El valor resultante se almacena en caché y se le proporciona en su devolución de llamada.
void Reset ( )Restablece el estado interno, el estado de es decir, las llamadas en el proceso y la memoria caché interna (en caso de que el objeto ServiceConConconeda devuelva los valores).
bool IsExecuting ( )Devuelve si el objeto ServiceConConConDe está ejecutando actualmente una solicitud específica.
Los siguientes métodos solo están disponibles en objetos ServiceConConConconda que devuelven los valores.
void ResetCache ( )Restablece el caché interno. También llamado desde RESET ().
bool TryGetValue ( TArg key , out TValue value )Obtiene un valor del caché.
void Set ( TArg key , TValue value )Un acumulador de caché en el caso raro que puede necesitar manipular el caché manualmente.
void Remove ( TArg key )Elimina un valor del caché.
bool ContainsKey ( TArg key )Comprueba si existe una entrada en el caché.
TValue this [ TArg key ]Operador de matriz para configurar y obtener entradas de caché. Lanza un KeyNotFoundException en el Getter si no existe una entrada.
IEnumerator < KeyValuePair < TArg , TValue > > GetEnumerator ( )
IEnumerator IEnumerable . GetEnumerator ( )Enumeradores para el caché interno. Le permite usar los objetos en las declaraciones de Foreach y Linq.
void Dispose ( ) Elimina el caché interno en caso de que bool IsCacheShared sea falso. Si es un caché compartido, solo llamará ResetCache .
TValue Value ;Solo en ReturnsValue <Value>. Este es el objeto en caché único.
Las siguientes propiedades solo están disponibles en los objetos ServiceConconcurrence que devuelven los valores.
MemoryCacheEntryOptions CacheEntryOptions ;Estas opciones se usan internamente cuando se almacena un valor. La edición de estos le permite establecer expiración, tamaños de caché, etc. Consulte MemoryCacheOptions para obtener más detalles.
bool IsCacheShared ;Getter solamente. Denota si el caché se comparte o no.
Ejecutar () acepta un convertidor de valor opcional, que puede modificar el valor recuperado antes de devolverlo y almacenarlo en caché. Esto está disponible solo en los objetos ServiceConConConconda que devuelven los 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 } " ;
) ;
}Los objetos de ServiceConConcurrence también aceptan un parámetro adicional que declare el tipo de valor de la solicitud interna. Cuando esto se especifica, el convertidor de valor también se convertirá entre el tipo de origen y el tipo de destino. Esto es útil si la solicitud invocada por Execute () es de un tipo diferente al campo de respaldo deseado.
// 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 ( ) ;
}
}