2010-12-06 13 views
3

J'ai le code suivant avec lequel je veux réaliser ce qui suit.Verrouiller un objet dans une opération asynchrone

  1. Vérifiez si une valeur est dans le cache
  2. Si dans le cache, obtenir la valeur de celle-ci et procéder
  3. Sinon dans le cache, effectuez la logique pour entrer dans le cache, mais faire async que l'opération pour faire cela peut prendre une longue période de temps et je ne veux pas tenir l'utilisateur

Comme vous le verrez dans mon code, je place un verrou sur le cache dans le fil asynchrone. Ma configuration est-elle en dessous du thread sécurisé? Et en plaçant le verrou, cela signifie que le cache ne sera pas accessible aux autres threads à lire à partir du cache tant que l'opération asynchrone aura lieu. Je ne veux pas d'une circonstance où le cache est verrouillé dans un thread asynchrone empêchant d'autres demandes d'y accéder.

Il est également possible que la même requête soit appelée par plusieurs threads, d'où le verrou.

Toutes les recommandations pour améliorer le code seraient excellentes.

// Check if the value is in cache 
     if (!this.Cache.Contains(key)) 
     { 
      // Perform processing of files async in another thread so rendering is not slowed down 
      ThreadPool.QueueUserWorkItem(delegate 
      { 
       lock (this.Cache) 
       { 
        if (!this.Cache.Contains(key)) 
        { 
         // Perform the operation to get value for cache here 
         var cacheValue = operation(); 

         this.Cache.Add(key, cacheValue); 
        } 
       } 
      }); 

      return "local value"; 
     } 
     else 
     { 
      // Return the string from cache as they are present there 
      return this.Cache.GetFilename(key); 
     } 

Remarque: this.Cache représente un objet de cache.

L'application est une application web sur .net 3.5.

Répondre

1

que diriez-vous de changer le délégué à ressembler à ceci?:

var cacheValue = operation(); 
lock (this.Cache) 
      { 
       if (!this.Cache.Contains(key)) 
       { 
        // Perform the operation to get value for cache here 

        this.Cache.Add(key, cacheValue); 
       } 
      } 

Ce type de codage verrouille le dictionnaire pour un très peu de temps. Vous pouvez également essayer d'utiliser ConcurrentDictionary qui ne verrouille pas du tout.

Alex.

+0

Oui, je pense que c'est une meilleure option pour déplacer la position du verrou juste à l'ajout au cache. Cela gérera également la situation où la même clé peut être ajoutée deux fois si le thread asynchrone est déclenché plus d'une fois. Que voulez-vous dire quand vous dites ConcurrentDictionary? – amateur

+0

Vous souhaitez empêcher plusieurs appels à l'opération() bien que dans le cas où il y a des échecs de cache sur la même valeur. Par exemple, si 3 threads ont besoin de la même valeur, cette solution appellera operation() 3 fois, alors que la solution d'origine n'appellera qu'une seule fois. –

+0

@Nail: 'ConcurrentDictionary' est un type de collection introduit avec .NET 4.0. C'est dans l'espace de noms 'System.Collections.Concurrent'. –

0

Ressemble à une solution standard, à l'exception de la récupération dans le fil d'arrière-plan. Il sera thread-safe tant que tous les autres bits du code qui utilisent le cache prennent également un verrou sur la même référence de cache avant de le modifier. À partir de votre code, d'autres threads pourront toujours lire à partir du cache (ou y écrire s'ils ne retirent pas un verrou(). Le code ne bloquera que le point où une instruction lock() est . rencontré

Est-ce que le retour « de valeur locale » logique vous avez pas besoin de récupérer l'élément dans cette fonction de toute façon dans le cas d'un défaut de cache

+0

Cest ma faute de confusion - le retour « valeur locale » est juste pour illustrer le fait que la valeur par défaut sera retourné tandis que l'autre fil fonctionne. Si un autre thread rencontre cette méthode avec une clé différente, peut-il accéder au cache pendant que le premier thread traite son appel asynchrone? Aussi dois-je inclure le verrou juste autour de l'ajout au cache plutôt que l'opération entière? Pour gérer un scénario dans lequel le thread 1 exécute l'opération et le thread 2 recherche la même clé et déclenche l'appel asynchrone. – amateur

+0

Oui, c'est possible. Je garderais cela comme vous l'avez fait (ne l'incluez qu'à l'étape où vous avez déterminé une cache manquante) exactement pour la raison que vous avez mentionnée. –

+0

Tant que "valeur locale" a du sens dans le contexte, l'utilisateur n'aura pas la valeur qui devrait être dans le cache à moins d'appeler de nouveau la méthode. Est-ce une application Web ou Windows? –

1

Votre code présente plusieurs problèmes. Les problèmes incluent: appeler Cache.Contains en dehors d'un lock alors que d'autres threads peuvent modifier la collection; invoquant operation dans un lock pouvant provoquer des blocages; etc.

Voici une implémentation thread-safe d'un cache qui satisfait à toutes vos exigences:

class Cache<TKey, TValue> 
{ 
    private readonly ConcurrentDictionary<TKey, Task<TValue>> items; 

    public Cache() 
    { 
     this.items = new ConcurrentDictionary<TKey, Task<TValue>>(); 
    } 

    public Task<TValue> GetAsync(TKey key, Func<TKey, TValue> valueFactory) 
    { 
     return this.items.GetOrAdd(key, 
      k => Task.Factory.StartNew<TValue>(() => valueFactory(k))); 
    } 
} 

La méthode GetAsync fonctionne comme suit: D'abord, il vérifie s'il y a une tâche dans le items dictionnaire pour la donnée clé. Si cette tâche n'existe pas, il exécute valueFactory de manière asynchrone sur le ThreadPool et stocke l'objet Tâche qui représente l'opération asynchrone en attente dans le dictionnaire.L'appel de code GetAsync peut attendre que la tâche se termine, ce qui renvoie la valeur calculée par valueFactory. Tout se passe de manière asynchrone, non bloquante, sans danger pour les threads.

Exemple d'utilisation:

var cache = new Cache<string, int>(); 

Task<int> task = cache.GetAsync("Hello World", s => s.Length); 

// ... do something else ... 

task.Wait(); 
Console.WriteLine(task.Result); 
+0

Merci, mais pourriez-vous expliquer cette ligne que je ne comprends pas tranquille -> return this.items.GetOrAdd (clé, k => Task.Factory.StartNew (() => valeurFactory (k))); – amateur

+0

Votre exemple fonctionnera-t-il dans .net 3.5? Merci de votre aide. – amateur

+1

ce code ne fonctionnera pas avec .net 3.5, et votre tâche asynchrone chère peut encore être exécutée plus d'une fois; pour garantir que la tâche asynchrone ne s'exécute qu'une seule fois, combinez concurrentDictionary avec Lazy kite