2016-07-28 2 views
2

Ich hatte eine komplexe Aufgabe/lock basierte Chaos für die Durchführung einer "langen" Datenoperation, und ich versuche, es durch eine asynchrone/warten zu ersetzen. Ich bin neu in Async-Erwartung, also mache ich mir Sorgen, dass ich einige große Fehler mache.async & erwarten - mehrere Anrufe auf die gleiche Methode - Sperren/Warten aufeinander?

Um die Dinge zu vereinfachen, hat meine UI ein paar Seiten, die auf den gleichen Daten beruhen. Jetzt muss ich diese Daten nur einmal abrufen. Also cache ich es, und weitere Aufrufe greifen einfach aus dem Cache "CachedDataObjects", anstatt jedes Mal den langen Aufruf auszuführen.

Gefallen Sie diesen (halb-Pseudo-Code):

private Dictionary<Guid,List<Data>> CachedDataObjects; 

    public async Task<List<Data>> GetData(Guid ID) 
    { 
     List<Data> data = null; 

     //see if we have any cached 
     CachedDataObjects.TryGetValue(ID, out data); 

     if (data == null) 
     { 
      if (ConnectedToServer) 
      { 
       data = new List<Data>(); 
       await Task.Run(() => 
       { 
        try 
        { 
         //long data call 
         data = Service.GetKPI(ID); 
        } 
        catch (Exception e) 
        { 
         //deal with errors (passes an action to do if resolved) 
         PromptForConnection(new Task(async() => { data = await GetData(ID); }), e); 
        } 
       }); 
      } 

      CachedDataObjects.Add(ID, data); 
     } 
     return data; 
    } 

jedoch durch die Art der asynchronen Anrufe, dies durch die beiden Seiten genannt Methode erhalten, wenn sie ausgelöst werden.

Als Ergebnis gibt es eine Ausnahme - ein Element mit der ID wurde bereits zum Wörterbuch hinzugefügt. Selbst wenn ich dieses Problem behoben habe, ist das zugrunde liegende Problem immer noch vorhanden. Die Datenobjekte würden verschiedene Versionen sein, ich mache zwei Netzwerkanrufe, wo ich nur einen haben sollte.

Zuvor habe ich eine Lösung gehackt, indem ich die ganze Methode in eine Lock-Anweisung eingekapselt habe - und dadurch nur einen einzigen erlaubt habe ruf an. Alle meine Daten werden in Hintergrundarbeitern geladen, der erste hat den Anruf gemacht, und sobald er fertig war, wurden die anderen freigeschaltet, um den schnellen Zugriff auszuführen.

Aber ich kann Sperre nicht in einer asynchronen Methode verwenden, und die Lösung fühlte sich sowieso nicht gut.

Gibt es eine Möglichkeit mit asynchronen Methoden zu warten auf andere asynchrone Anrufe zu beenden?

+0

in diesem Fall Caching sinnvoll? –

+0

@PankajGupta Sorry, kleiner Fehler im Code. "CachedDataObjects" wird jetzt durchgehend als Wörterbuch verwendet. – Joe

+0

, sorry bekam nicht. –

Antwort

3

Ihr Problem besteht darin, dass Sie die Aufgabe ausführen, bevor Sie sie dem Wörterbuch hinzufügen. In diesem Fall sollten Sie die Aufgabe dem Wörterbuch hinzuzufügen, so dass die nächste Seite Aufruf dieser Methode wird die gleiche Aufgabe erhalten:

public Task<List<Data>> GetData(Guid ID) 
{ 
    Task<List<Data>> task = null; 
    CachedDataObjects.TryGetValue(ID, out task); 
    if (task == null) 
    { 
    if (ConnectedToServer) 
    { 
     task = Task.Run(() => 
     { 
     try 
     { 
      //long data call 
      return Service.GetKPI(ID); 
     } 
     catch (Exception e) 
     { 
      //deal with errors 
     } 
     }); 
    } 
    DataObjects.Add(ID, task); 
    } 
    return task; 
} 

Dies wird die Aufgabe zwischenzuspeichern. Wenn jedoch //deal with errors Ausnahmen weitergibt, wird diese Ausnahme ebenfalls zwischengespeichert.

Um dies zu vermeiden, können Sie komplexeren Code verwenden, oder Sie können meine AsyncLazy<T> type annehmen:

private readonly ConcurrentDictionary<Guid, AsyncLazy<List<Data>>> CachedDataObjects; 
public Task<List<Data>> GetData(Guid ID) 
{ 
    var lazy = CachedDataObjects.GetOrAdd(ID, id => 
     new AsyncLazy<List<Data>>(() => Task.Run(() => 
     { 
     try 
     { 
      return Service.GetKPI(ID); 
     } 
     catch (Exception e) 
     { 
      //deal with errors 
      throw; 
     } 
     }, AsyncLazyFlags.RetryOnFailure | AsyncLazyFlags.ExecuteOnCallingThread))); 
    return lazy.Task; 
} 
+0

Netter Ansatz, um die Aufgabe anstelle des Ergebnisses zwischenzuspeichern. Soweit ich sehen kann, würde es immer noch eine Sperre geben müssen, um die Race Conditions zwischen Cachetest und Zuweisung konsequent zu umgehen? – grek40

+0

@ grek40: Wenn Sie ein nicht gleichzeitiges Wörterbuch verwenden und auf diese Methode von mehreren Threads aus zugreifen, dann wäre ja eine Sperre erforderlich. –

+0

Das ist eine großartige Idee! – Joe