2016-02-10 4 views
5

Ich habe eine Komponente von Drittanbietern, die "teuer" ist, um hochzufahren. Diese Komponente ist nicht Thread-sicher. Diese Komponente wird (vorerst) in einem WCF-Dienst gehostet, also ... jedes Mal, wenn ein Anruf in den Dienst kommt, muss ich die Komponente neu aufbauen."Fixed"/"Load Balanced" C# Thread-Pool?

Was ich stattdessen tun möchte, ist ein Pool von sagen wir 16 Threads, die jeweils ihre eigene Kopie der Komponente hochfahren und einen Mechanismus haben, um die Methode aufzurufen und sie an einen der 16 Threads verteilen zu lassen der zurückgegebene Wert

So etwas Einfaches wie:

var response = threadPool.CallMethod(param1, param2); 

Sein feine für den Anruf zu blockieren, bis er eine Antwort bekommt, als ich die Antwort auf vorgehen muß.

Irgendwelche Vorschläge? Vielleicht überschätze ich es und eine ConcurrentQueue, die von 16 Threads bedient wird, würde den Job tun, aber jetzt sicher, wie der Rückgabewert der Methode an den Aufrufer zurückgegeben würde?

+0

Müssen wissen, mehr über die Komponente. 1) Ist es eine COM-Komponente? 2) Wie hosten Sie den WCF-Dienst? IIS? In einem Windows-Dienst? Konsolen-App? 3) Was ist die Instanzkontrolle des WCF-Dienstes? Singleton? Per Anruf? Pro Sitzung? – MickyD

+2

Zusätzlich zu dem, was @MickyD gesagt hat, löst das Einfädeln eines Aufrufs zum Erstellen einer neuen Instanz der Komponente Ihr Thread-Sicherheitsproblem nicht (ich denke, das macht es sogar noch schlimmer). – AWinkle

+3

Es klingt wie das, was Sie wirklich brauchen, ist ein Objekt-Pool, kein Thread-Pool. –

Antwort

2

WCF wird bereits den Thread-Pool verwenden, um seine Ressourcen zu verwalten. Wenn Sie also eine Schicht Thread-Management hinzufügen, wird es nur schlecht gehen. Vermeiden Sie dies wenn möglich, da Sie bei Ihren Serviceanrufen in Konflikt geraten.

Was ich in Ihrer Situation tun würde, ist einfach eine einzigenThreadLocal oder Thread statische, die mit Ihrem teuren Objekt einmal initialisiert bekommen würde. Danach wäre es für den Thread-Pool-Thread verfügbar.

Das geht davon aus, dass Ihr Objekt in einem MTA-Thread in Ordnung ist; Ich schätze, es kommt von deinem Post, da es sich anhört, als würden die Dinge gerade funktionieren, aber nur langsam.

Es besteht die Sorge, dass zu viele Objekte erstellt werden und Sie zu viel Speicher verwenden, da der Pool zu groß wird. Prüfen Sie jedoch, ob dies in der Praxis der Fall ist, bevor Sie etwas anderes tun. Dies ist eine sehr einfache Strategie, die so einfach zu testen ist. Werden Sie nur komplexer, wenn Sie es wirklich brauchen.

+0

Ja, ich denke, was ich suche, ist ein Objekt-Pool, kein Thread-Pool. Das Objekt ist nicht threadsicher. Wenn mehrere Threads auf dieselbe Instanz zugreifen, stürzt sie ab. ThreadLocal ist nicht nützlich, da ein WCF-Dienst die Threads erstellt und zerstört. – SledgeHammer

+0

Haben Sie das tatsächlich in der Praxis getestet? Das könnte der Fall sein, aber dieser Typ meint, dass es nur Threads aus dem Thread-Pool zieht: http://stackoverflow.com/questions/5288991/destroy-a-wcf-thread. Er sagt, dass man keine Thread-Statik verwenden soll, um den Zustand zu speichern, aber hier speichert man keinen Zustand. – briantyler

2

In erster Linie stimme ich @briantyler: ThreadLocal<T> oder Thread statische Felder ist wahrscheinlich, was Sie wollen. Sie sollten damit als Ausgangspunkt gehen und andere Optionen erwägen, wenn es Ihren Bedürfnissen nicht entspricht.

Eine komplizierte, aber flexible Alternative ist ein Singleton-Objektpool. In seiner einfachsten Form wird Ihr Pool-Typ wie folgt aussehen:

public sealed class ObjectPool<T> 
{ 
    private readonly ConcurrentQueue<T> __objects = new ConcurrentQueue<T>(); 
    private readonly Func<T> __factory; 

    public ObjectPool(Func<T> factory) 
    { 
     __factory = factory; 
    } 

    public T Get() 
    { 
     T obj; 
     return __objects.TryDequeue(out obj) ? obj : __factory(); 
    } 

    public void Return(T obj) 
    { 
     __objects.Enqueue(obj); 
    } 
} 

Dies scheint nicht schrecklich nützlich, wenn Sie vom Typ denken T in Bezug auf die primitive Klassen oder Strukturen (dh ObjectPool<MyComponent>), wie der Pool hat keine Threading-Steuerelemente eingebaut. Aber Sie können Ihren Typ T für einen Lazy<T> oder Task<T> Monad ersetzen, und erhalten genau das, was Sie wollen.

Pool Initialisierung:

Func<Task<MyComponent>> factory =() => Task.Run(() => new MyComponent()); 
ObjectPool<Task<MyComponent>> pool = new ObjectPool<Task<MyComponent>>(factory); 

// "Pre-warm up" the pool with 16 concurrent tasks. 
// This starts the tasks on the thread pool and 
// returns immediately without blocking. 
for (int i = 0; i < 16; i++) { 
    pool.Return(pool.Get()); 
} 

Verbrauch:

// Get a pooled task or create a new one. The task may 
// have already completed, in which case Result will 
// be available immediately. If the task is still 
// in flight, accessing its Result will block. 
Task<MyComponent> task = pool.Get(); 

try 
{ 
    MyComponent component = task.Result; // Alternatively you can "await task" 

    // Do something with component. 
} 
finally 
{ 
    pool.Return(task); 
} 

Diese Methode ist komplexer als Ihre Komponente in einem ThreadLocal oder Gewinde statisches Feld beibehalten, aber wenn Sie brauchen etwas Phantasie wie Begrenzung zu tun Bei der Anzahl der Pool-Instanzen kann die Pool-Abstraktion sehr nützlich sein.

EDIT

Grund Pool-Implementierung mit einem Get, welche Blöcke einmal am Pool wurde abgelassen "von X-Instanzen gesetzt fixed":

public sealed class ObjectPool<T> 
{ 
    private readonly Queue<T> __objects; 

    public ObjectPool(IEnumerable<T> items) 
    { 
     __objects = new Queue<T>(items); 
    } 

    public T Get() 
    { 
     lock (__objects) 
     { 
      while (__objects.Count == 0) { 
       Monitor.Wait(__objects); 
      } 

      return __objects.Dequeue(); 
     } 
    } 

    public void Return(T obj) 
    { 
     lock (__objects) 
     { 
      __objects.Enqueue(obj); 

      Monitor.Pulse(__objects); 
     } 
    } 
} 
+0

Ja, ich denke, das ist der richtige Weg. Ich muss nur den Objektpool optimieren, um eine feste Menge von X-Instanzen zu haben, anstatt sie im laufenden Betrieb ohne Begrenzung zu erstellen. – SledgeHammer

+0

@SledgeHammer, in diesem Fall können Sie eine 'BlockingCollection ' als Objektpool verwenden. Wenn Sie jemals in eine Situation geraten, in der alle gepoolten Objekte verpachtet wurden, wird der nächste Verbraucher auf 'Take()' blockieren, bis eine der Instanzen zurückgegeben wird (über 'Hinzufügen'). –

+0

Ich würde auch einen Pool betrachten, bei dem 'Return' die zurückgegebene Instanz verwirft, wenn die Anzahl der Poolinstanzen über den gewünschten Wert hinaus wächst. Dies ermöglicht die Zuteilung neuer Instanzen, wenn dies absolut notwendig ist, aber jeder Überschuss wird gesammelt, wodurch ein unbegrenztes Wachstum verhindert wird. –