2016-06-10 14 views
0

Angenommen, ich möchte die parallele Ausführung von Code zulassen, aber anderen Code warten, bis alle diese Vorgänge abgeschlossen sind.Vorläufige Sperren in C#?

Lassen Sie sich ein softlock neben lock vorstellen:

public static class MySimpleCache 
{ 
    private static readonly SynchronizedCollection<KeyValuePair<string, string>> Collection = new SynchronizedCollection<KeyValuePair<string, string>>(); 

    public static string Get(string key, Func<string> getter) 
    { 
     // Allow parallel enumerations here, 
     // but force modifications to the collections to wait. 
     softlock(Collection.SyncRoot) 
     { 
      if (Collection.Any(kvp => kvp.Key == key)) 
      { 
       return Collection.First(kvp => kvp.Key == key).Value; 
      } 
     } 

     var data = getter(); 

     // Wait for previous soft-locks before modifying the collection and let subsequent softlocks wait 
     lock (Collection.SyncRoot) 
     { 
      Collection.Add(new KeyValuePair<string, string>(key, data)); 
     } 
     return data; 
    } 
} 

Gibt es ein Design-Muster oder Sprache/framework Funktionen in C#/NET dies auf einfache und zuverlässige Art und Weise zu erreichen, oder müßte man. um dies von Grund auf umzusetzen?

Ich bin derzeit auf .NET 3.5 beschränkt und ich interessiere mich hauptsächlich für das konzeptionelle Problem, nicht so sehr in anderen möglichen Sammlungen, die das Beispiel an sich lösen könnten.

+3

Klingt wie Sie brauchen [ReaderWriterLockSlim] (https://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim (v = vs.110) aspx), die beginnen kann eine Lesersperre, und zu einer Schreibsperre aufgerüstet werden. –

+0

Ihre Suche ist etwas ineffizient, Sie aufzählen zweimal, könnten Sie 'var result = Collection.FirstOrDefault (kvp => kvp.Key == Schlüssel); if (Ergebnis! = Standard (KeyValuePair )) {return result.Value; } 'und mache die Suche nur einmal. –

+0

@MatthewWatson Danke, das sieht sehr gut aus, ich werde damit herumspielen. – Alex

Antwort

2

In Situationen wie diesem können Sie eine ReaderWriterLockSlim verwenden, es wird mehrere Leser erlauben, bis jemand schreiben will, es blockiert dann alle Leser und erlaubt nur einen einzigen Schreiber durch.

public static class MySimpleCache 
{ 
    private static readonly SynchronizedCollection<KeyValuePair<string, string>> Collection = new SynchronizedCollection<KeyValuePair<string, string>>(); 
    private static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim(); 

    public static string Get(string key, Func<string> getter) 
    { 
     //This allows multiple readers to run concurrently. 
     Lock.EnterReadLock(); 
     try 
     { 
      var result = Collection.FirstOrDefault(kvp => kvp.Key == key); 
      if (!Object.Equals(result, default(KeyValuePair<string, string>))) 
      { 
       return result.Value; 
      } 
     } 
     finally 
     { 
      Lock.ExitReadLock(); 
     } 


     var data = getter(); 

     //This blocks all future EnterReadLock(), once all finish it allows the function to continue 
     Lock.EnterWriteLock(); 
     try 
     { 
      Collection.Add(new KeyValuePair<string, string>(key, data)); 
      return data; 
     } 
     finally 
     { 
      Lock.ExitWriteLock(); 
     } 
    } 
} 

Sie können jedoch überprüfen wollen, um zu sehen, während Sie in dem Warte die Schreibsperre, jemanden zu nehmen sonst möglicherweise den Datensatz in in den Cache eingegeben, in diesem Fall, dass Sie ein EnterUpgradeableReadLock() verwenden können, ermöglicht diese unbegrenzte Menschen innerhalb von EnterReadLock() zu sein, aber nur eine einzelne Person kann im Upgrade-Schloss sein (und es wird immer noch keine Schreibsperren geben). Das Upgrade-fähige Schloss ist nützlich, wenn Sie wissen, dass Sie wahrscheinlich schreiben werden, aber es gibt eine Möglichkeit, nicht zu schreiben.

public static class MySimpleCache 
{ 
    private static readonly SynchronizedCollection<KeyValuePair<string, string>> Collection = new SynchronizedCollection<KeyValuePair<string, string>>(); 
    private static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim(); 

    public static string Get(string key, Func<string> getter) 
    { 
     //This allows multiple readers to run concurrently. 
     Lock.EnterReadLock(); 
     try 
     { 
      var result = Collection.FirstOrDefault(kvp => kvp.Key == key); 
      if (!Object.Equals(result, default(KeyValuePair<string, string>))) 
      { 
       return result.Value; 
      } 
     } 
     finally 
     { 
      Lock.ExitReadLock(); 
     } 

     //This allows unlimited EnterReadLock to run concurrently, but only one thread can be in upgrade mode, other threads will block. 
     Lock.EnterUpgradeableReadLock(); 
     try 
     { 
      //We need to check to see if someone else filled the cache while we where waiting. 
      var result = Collection.FirstOrDefault(kvp => kvp.Key == key); 
      if (!Object.Equals(result, default(KeyValuePair<string, string>))) 
      { 
       return result.Value; 
      } 


      var data = getter(); 

      //This blocks all future EnterReadLock(), once all finish it allows the function to continue 
      Lock.EnterWriteLock(); 
      try 
      { 
       Collection.Add(new KeyValuePair<string, string>(key, data)); 
       return data; 
      } 
      finally 
      { 
       Lock.ExitWriteLock(); 
      } 
     } 
     finally 
     { 
      Lock.ExitUpgradeableReadLock(); 
     } 
    } 
} 

P. S. Sie haben in einem Kommentar erwähnt, dass der Wert null sein könnte, also würde FirstOrDefault() nicht funktionieren. Verwenden Sie in diesem Fall eine Erweiterungsmethode, um eine TryFirst() Funktion zu erstellen.

public static class ExtensionMethods 
{ 
    public static bool TryFirst<T>(this IEnumerable<T> @this, Func<T, bool> predicate, out T result) 
    { 
     foreach (var item in @this) 
     { 
      if (predicate(item)) 
      { 
       result = item; 
       return true; 
      } 
     } 
     result = default(T); 
     return false; 
    } 
} 

//Used like 
Lock.EnterReadLock(); 
try 
{ 
    KeyValuePair<string, string> result; 
    bool found = Collection.TryFirst(kvp => kvp.Key == key, out result); 
    if (found) 
    { 
     return result.Value; 
    } 
} 
finally 
{ 
    Lock.ExitReadLock(); 
}