2012-06-17 3 views
7

Ich benutze die TPL, um neue Aufgaben zum System Thread-Pool mit der Funktion Task.Factory.StartNew() hinzufügen. Das einzige Problem ist, dass ich eine Menge Threads hinzufüge und ich denke, dass es zu viele für meinen Prozessor schafft. Gibt es eine Möglichkeit, eine maximale Anzahl von Threads in diesem Threadpool festzulegen?Verwenden von TPL, wie setze ich eine maximale Threadpoolgröße

+3

Sie nicht nur denken, , finde heraus, ob es tatsächlich stimmt. – svick

+0

Irgendwelche Codebeispiele? –

Antwort

12

Der Standardwert TaskScheduler (erhalten von TaskScheduler.Default) ist vom Typ (interne Klasse) ThreadPoolTaskScheduler. Diese Implementierung verwendet die Klasse ThreadPool, um Aufgaben in die Warteschlange zu stellen (wenn die Task nicht mit TaskCreationOptions.LongRunning erstellt wird - in diesem Fall wird für jede Aufgabe ein neuer Thread erstellt).

Also, wenn Sie die Anzahl der Threads zur Verfügung zu Task Objekte über new Task(() => Console.WriteLine("In task")) erstellt beschränken möchten, können Sie die verfügbaren Threads im globalen Threadpool wie folgt begrenzen:

// Limit threadpool size 
int workerThreads, completionPortThreads; 
ThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads); 
workerThreads = 32; 
ThreadPool.SetMaxThreads(workerThreads, completionPortThreads); 

Der Aufruf von ThreadPool.GetMaxThreads() wird getan, um Vermeiden Sie Schrumpfen der completionPortThreads.

Beachten Sie, dass dies eine schlechte Idee sein kann - da alle Aufgaben ohne Angabe eines Scheduler und eine beliebige Anzahl von anderen Klassen verwenden, um den Standardthreadpool, könnte die Größe zu niedrig einstellen Nebenwirkungen verursachen: Starvation usw.

3

Sie sollten zuerst Ihre Leistungsprobleme untersuchen. Es gibt verschiedene Probleme, die zu einer verminderten Nutzung führen:

  • Planen lang laufende Aufgaben ohne die LongRunningTask Option
  • Der Versuch, mehr als zwei gleichzeitige Verbindungen zu derselben Web-Adresse zu öffnen
  • für die Sperrung des Zugangs zu den gleiche Ressource
  • der Versuch, den UI-Thread mit Invoke() von mehreren Threads

auf jeden Fall zugreifen Sie eine Skalierbarkeit Problem haben, die nicht einfach angesprochen werden können durch Reduzieren der Anzahl gleichzeitiger Aufgaben. Ihr Programm wird möglicherweise in Zukunft auf einem Zwei-, Vier- oder Acht-Kern-Rechner ausgeführt. Wenn Sie die Anzahl der geplanten Aufgaben begrenzen, wird die CPU-Ressourcen verschwendet.

+1

Danke für den Downvote, zu erklären? Ein häufiger Fehler ist die Annahme, dass Tasks gleich Threads sind und versuchen, die Anzahl der Tasks zu begrenzen, was zu noch größeren Performance-Problemen führt. –

-1

In der Regel sollte der TPL-Scheduler die Anzahl der gleichzeitig ausgeführten Tasks gut ausschöpfen, aber wenn Sie wirklich die Kontrolle darüber haben möchten, zeigt My blog post, wie Sie dies sowohl mit Tasks als auch mit Aktionen tun und ein Beispielprojekt zum Download bereitstellen und lauf, um beide in Aktion zu sehen.

Ein Beispiel, wann Sie explizit begrenzen möchten, wie viele Aufgaben gleichzeitig ausgeführt werden, ist, wenn Sie Ihre eigenen Dienste aufrufen und Ihren Server nicht überlasten möchten.

Für das, was Sie beschreiben, klingt es so, als könnten Sie mehr davon profitieren, sicherzustellen, dass Sie async/await mit Ihren Aufgaben verwenden, um unnötigen Threadverbrauch zu vermeiden. Dies hängt jedoch davon ab, ob Sie CPU-gebundene Arbeit oder IO-gebundene Arbeit in Ihren Aufgaben ausführen. Wenn es IO-gebunden ist, können Sie davon profitieren, async/await zu verwenden.

Unabhängig davon, Sie gefragt, wie Sie die Anzahl der Aufgaben, die gleichzeitig ausgeführt werden können, begrenzen, so hier ist ein Code, um zu zeigen, wie es mit Aktionen und Aufgaben zu tun.

Mit Aktionen

Bei der Verwendung von Aktionen können Sie die integrierten .NET Parallel.Invoke-Funktion verwenden. Hier beschränken wir uns darauf, maximal 3 Threads parallel zu laufen.

var listOfActions = new List<Action>(); 
for (int i = 0; i < 10; i++) 
{ 
    // Note that we create the Action here, but do not start it. 
    listOfActions.Add(() => DoSomething()); 
} 

var options = new ParallelOptions {MaxDegreeOfParallelism = 3}; 
Parallel.Invoke(options, listOfActions.ToArray()); 

Mit Aufgaben

Da Sie hier Aufgaben allerdings nicht verwenden, gibt es keine integrierte Funktion. Sie können jedoch das verwenden, das ich in meinem Blog zur Verfügung stelle.

/// <summary> 
    /// Starts the given tasks and waits for them to complete. This will run, at most, the specified number of tasks in parallel. 
    /// <para>NOTE: If one of the given tasks has already been started, an exception will be thrown.</para> 
    /// </summary> 
    /// <param name="tasksToRun">The tasks to run.</param> 
    /// <param name="maxTasksToRunInParallel">The maximum number of tasks to run in parallel.</param> 
    /// <param name="cancellationToken">The cancellation token.</param> 
    public static void StartAndWaitAllThrottled(IEnumerable<Task> tasksToRun, int maxTasksToRunInParallel, CancellationToken cancellationToken = new CancellationToken()) 
    { 
     StartAndWaitAllThrottled(tasksToRun, maxTasksToRunInParallel, -1, cancellationToken); 
    } 

    /// <summary> 
    /// Starts the given tasks and waits for them to complete. This will run, at most, the specified number of tasks in parallel. 
    /// <para>NOTE: If one of the given tasks has already been started, an exception will be thrown.</para> 
    /// </summary> 
    /// <param name="tasksToRun">The tasks to run.</param> 
    /// <param name="maxTasksToRunInParallel">The maximum number of tasks to run in parallel.</param> 
    /// <param name="timeoutInMilliseconds">The maximum milliseconds we should allow the max tasks to run in parallel before allowing another task to start. Specify -1 to wait indefinitely.</param> 
    /// <param name="cancellationToken">The cancellation token.</param> 
    public static void StartAndWaitAllThrottled(IEnumerable<Task> tasksToRun, int maxTasksToRunInParallel, int timeoutInMilliseconds, CancellationToken cancellationToken = new CancellationToken()) 
    { 
     // Convert to a list of tasks so that we don&#39;t enumerate over it multiple times needlessly. 
     var tasks = tasksToRun.ToList(); 

     using (var throttler = new SemaphoreSlim(maxTasksToRunInParallel)) 
     { 
      var postTaskTasks = new List<Task>(); 

      // Have each task notify the throttler when it completes so that it decrements the number of tasks currently running. 
      tasks.ForEach(t => postTaskTasks.Add(t.ContinueWith(tsk => throttler.Release()))); 

      // Start running each task. 
      foreach (var task in tasks) 
      { 
       // Increment the number of tasks currently running and wait if too many are running. 
       throttler.Wait(timeoutInMilliseconds, cancellationToken); 

       cancellationToken.ThrowIfCancellationRequested(); 
       task.Start(); 
      } 

      // Wait for all of the provided tasks to complete. 
      // We wait on the list of "post" tasks instead of the original tasks, otherwise there is a potential race condition where the throttler&#39;s using block is exited before some Tasks have had their "post" action completed, which references the throttler, resulting in an exception due to accessing a disposed object. 
      Task.WaitAll(postTaskTasks.ToArray(), cancellationToken); 
     } 
    } 

Und dann die Liste der Aufgaben Erstellen und Aufrufen der Funktion, sie laufen zu lassen, mit maximal 3 gleichzeitige sagen zu einer Zeit, können Sie dies tun:

var listOfTasks = new List<Task>(); 
for (int i = 0; i < 10; i++) 
{ 
    var count = i; 
    // Note that we create the Task here, but do not start it. 
    listOfTasks.Add(new Task(() => Something())); 
} 
Tasks.StartAndWaitAllThrottled(listOfTasks, 3);