2012-04-05 2 views
1

Wir haben einen einfachen WCF-Dienst, der mit InstanceContextMode = Single und ConcurrencyMode = Multiple markiert ist. Für Methoden, die Listen zurückliefern, anstatt in unsere Datenbank zu gehen und die Liste zu füllen, wenn der Anruf getätigt wird, geben wir einfach eine lokale Kopie der Liste zurück. Die Liste wird von einem Hintergrund-Thread verwaltet, der alle 24 Stunden in unsere Datenbank gelangt und die Liste erneut auffüllt. Wir behandeln die Nebenläufigkeitsprobleme, indem wir ein Objekt an zwei Stellen sperren: innerhalb der Methode, die die Sammlung zurückgibt, und innerhalb der Methode, die die Sammlung füllt.Effiziente Sammlungsverwaltung in einem multi-threaded WCF-Dienst

Meine Frage ist, wie können wir das effizienter machen. Derzeit haben wir einen Engpass in Out-Methode „GetCustomers“, dass der Client nennen:

public List<ZGpCustomer> GetCustomers() 
{ 
    List<ZGpCustomer> customerListCopy = new List<ZGpCustomer>(); 
    lock (_customerLock) 
    { 
     customerListCopy = new List<ZGpCustomer>(_customers); 
    } 

    return customerListCopy; 
} 

Da „_Kunden“ gefüllt nur Stunden alle 24 wird, wie es scheint, wir sollten alle nur im Inneren des sperren müssen GetCustomers-Methode nur, wenn wir die Sammlung ändern. So wie es derzeit eingerichtet ist, stellen wir, wenn 1000 Anfragen gleichzeitig eingehen, die 1000 Anfragen in die Warteschlange, da nur 1 Thread gleichzeitig auf diese Methode zugreifen kann. Dies macht den Multithreading-Aspekt des Dienstes etwas nutzlos, nein?

Gibt es eine Best Practice für diese Art von Muster? Sollte ich eine geeignetere Datensammlung verwenden, um meine Objekte zu speichern? Wäre eine "BlockingCollection" besser geeignet?

+0

Ohne Beweis: Sie konnten einen Blick auf die Sammlungen in System.Collections.Concurrent, die ich denke, die Sache schneller als die Sperre umgehen: http://msdn.microsoft.com/de-de/ library/system.collections.concurrent.aspx – mattanja

Antwort

2

Dort ist sicherlich eine effizientere Möglichkeit, dies zu tun. Der Trick besteht darin, die Master-Referenz auf die Sammlung unveränderlich zu halten. Auf diese Weise müssen Sie den Zugriff auf Leserseite nie synchronisieren. Nur die Schreiberseite der Dinge braucht eine Sperre. Die Leserseite braucht nichts, was bedeutet, dass die Leser hochgradig parallel bleiben. Das einzige, was Sie tun müssen, ist die _customers Referenz als flüchtig zu markieren.

// Variable declaration 
object lockobj = new object(); 
volatile List<ZGpCustomer> _customers = new List<ZGpCustomer>(); 

// Writer 
lock (lockobj) 
{ 
    // Create a temporary copy. 
    var copy = new List<ZGpCustomer>(_customers); 

    // Modify the copy here. 
    copy.Add(whatever); 
    copy.Remove(whatever); 

    // Now swap out the references. 
    _customers = copy; 
} 

// Reader 
public List<ZGpCustomer> GetCustomers() 
{ 
    return new List<ZGpCustomer>(_customers); 
} 

Wenn Sie bereit sind, die Unterschrift von GetCustomers leicht ändern Sie die Unveränderlichkeit von _customers ausnutzt, und einen Nur-Lese-Wrapper zurück.

// Reader 
public IList<ZGpCustomer> GetCustomers() 
{ 
    return _customers.AsReadOnly(); 
} 
+0

Vielen Dank für Ihre Antwort ... Können Sie etwas mehr über Ihren zweiten Kommentar zur Rückgabe einer readonly Kopie der Kundenliste erklären? Welche Risiken leite ich * nicht * nach Ihrem Vorschlag bezüglich der Readonly-Kopie? – Thelonias

+1

Ich hätte * wrapper * anstelle von * copy * sagen sollen, um den Punkt zu verdeutlichen, dass ein tatsächlicher Kopiervorgang in 'GetCustomers' nicht benötigt wird, was die Performance noch weiter steigern würde. –

+0

Ah ok, Gotcha. Danke nochmal für die Info. Ich habe nie viel über den Wert von volatile geschaut, also nehme ich an, es ist Zeit! – Thelonias