2017-02-25 1 views
1

Vorwort: Ich bin mir bewusst, dass die Verwendung des ThreadPool (entweder über TPL oder direkt) für IO-Operationen is generally frowned upon weil IO unbedingt sequentiell ist, bezieht sich mein Problem auf "parallel IO" mit blockierenden Aufrufen Setzen Sie keine Async Methode ein.Verwenden von TPL mit parallel blockierenden IO-Operationen

Ich bin ein GUI-Tool zu schreiben, die Informationen über Computer im Netzwerk abruft, das dies tut (vereinfachte Code):

String[] computerNames = { "foo", "bar", "baz" }; 
foreach(String computerName in computerNames) { 

    Task.Factory 
     .StartNew(GetComputerInfo, computerName) 
     .ContinueWith(ShowOutputInGui, RunOnGuiThread); 

} 

private ComputerInfo GetComputerInfo(String machineName) { 

    Task<Int64>  pingTime = Task.Factory.StartNew(() => GetPingTime(machineName)); 
    Task<Process[]> processes = Task.Factory.StartNew(() => System.Diagnostics.Process.GetProcesses(machineName)); 
    // and loads more 

    Task.WaitAll(pingtime, processes, etc); 

    return new ComputerInfo(pingTime.Result, processes.Result, etc); 
} 

Wenn ich diesen Code ausführen Ich finde es eine überraschend lange dauert Menge Zeit zu laufen, verglichen mit dem alten sequentiellen Code, den ich hatte.

beachten, dass jede Aufgabe in der GetComputerInfo Verfahren ist völlig unabhängig von anderen um ihn herum (zB Ping Zeit getrennt von GetProcesses berechnet werden), doch, wenn ich einige Stopwatch Timing Anrufe eingefügt entdeckte ich, dass die einzelnen Teilaufgaben, wie der GetProcesses Anruf wurde nur bis zu 3000ms gestartet, nachdem GetComputerInfo aufgerufen worden war - es gibt einige große Verzögerung, die weitergehen.

Ich bemerkte, dass, wenn ich die Anzahl der äußeren parallelen Aufrufe in GetComputerInfo reduzieren (durch die Größe des Arrays computerNames zu reduzieren) die ersten Ergebnisse fast sofort zurückgegeben wurden. Einige der Computernamen sind für Computer, die ausgeschaltet sind, so genannte GetProcesses und PingTime nehmen eine sehr lange Zeit vor dem Zeitlimit (mein echter Code fängt die Ausnahmen). Dies liegt wahrscheinlich daran, dass die Offline-Computer Tasks blockieren, die ausgeführt werden, und die TPL beschränkt sie natürlich auf die Anzahl der CPU-Hardware-Threads (8).

Gibt es eine Möglichkeit, TPL mitzuteilen, dass die inneren Aufgaben (z. B. GetProcesses) keine äußeren Aufgaben blockieren (GetComputerInfo)?

(Ich habe versucht, die "Eltern/Kind" Aufgabe Anlage/Blockierung, aber es gilt nicht für meine Situation, wie ich nie untergeordnete Aufgaben nie explizit an übergeordneten Aufgaben anfügen, und die übergeordnete Aufgabe natürlich mit Task.WaitAll wartet sowieso) .

+0

Wahrscheinlich wäre besser, wenn 'GetComputerInfo () '' '' '' '' '_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ Warum nicht 'Task []' zurückgeben? – MickyD

+1

Ohne eine gute [mcve], die das Problem zuverlässig reproduziert, ist es schwierig, wenn nicht gar unmöglich, das Problem zu diagnostizieren. Beachten Sie jedoch, dass der Thread-Pool nicht unbegrenzt viele Threads bereithält. Im Leerlauf hat es nur eine Handvoll Threads max (gleich der Anzahl der CPU-Kerne) und startet nur alle halbe Sekunde bis Sekunde neue Threads (IIRC ist konfigurierbar und ich kann mich nicht an den Standard erinnern). Sie können Dinge helfen, indem Sie a) die async ping-Methode verwenden und b) 'ThreadPool.SetMinThreads()' verwenden, um die Anzahl der Threads im Leerlauf zu erhöhen, damit sie fertig sind, wenn Sie sie wollen. –

+0

Beachten Sie, dass der Aufruf von 'SetMinThreads()' ein bisschen wie ein Hack ist. Es ist bedauerlich, dass .NET keine asynchrone Version der 'GetProcesses()' Methode hat; aber ich sehe nicht einmal eine asynchrone Methode, dies in nativem Code zu tun. Wenn Sie wirklich wollen, dass die Operationen parallel sind, stecken Sie fest, die Threads selbst zu verwalten, denke ich. –

Antwort

2

Ich nehme an, dass Sie Ihre foreach-Schleife in einem Event-Handler haben, also sollten Sie es als async markieren, damit Sie Ihren anderen async aufrufen können. Danach sollten Sie Ihre GetComputerInfo zu tun asyncall the way down vorstellen.

Es gibt zusätzliche Fallstricke in Ihrem Code: StartNew is dangerous, da es Current Scheduler für Aufgaben verwendet, anstatt Default (so benötigen Sie andere Überladung). Leider benötigt diese Überladung einige weitere Parameter, daher wird der Code nicht so einfach sein. Die gute Nachricht ist, dass Sie immer noch, dass eine Überlastung zu dem Thread-Pool sagen, dass Ihre Aufgaben lange laufen so sollte es für sie einen eigenen Thread verwenden:

TaskCreationOptions.LongRunning

Gibt an, dass eine Aufgabe sein wird, ein lang andauernder, grobkörniger Betrieb mit weniger, größeren Komponenten als feinkörnige Systeme. Es gibt einen Hinweis auf die TaskScheduler, dass Überbuchung möglicherweise gerechtfertigt ist.

Bei Überbelegung können Sie mehr Threads erstellen als die verfügbare Anzahl an Hardware-Threads. Es gibt auch einen Hinweis für den Taskplaner, dass ein zusätzlicher Thread für die Task erforderlich sein kann, damit der Forward-Fortschritt anderer Threads oder Arbeitselemente in der lokalen Threadpoolwarteschlange nicht blockiert wird.

Auch sollten Sie die WaitAll Methode zu vermeiden, da es eine Sperroperation ist, so haben Sie 1 Faden weniger die eigentliche Arbeit zu tun. Sie möchten wahrscheinlich WhenAll verwenden.

Und schließlich für Ihr ComputerInfo Ergebnis Rückkehr Sie die Fortsetzung mit TaskCompletionSource Nutzung verwenden können, so könnte Ihr Code so etwas wie diese (Unterdrückungslogik auch hinzugefügt):

using System.Diagnostics; 

// handle event in fire-and-forget manner 
async void btn_Click(object sender, EventArgs e) 
{ 
    var computerNames = { "foo", "bar", "baz" }; 
    foreach(String computerName in computerNames) 
    { 
     var compCancelSource = new CancellationTokenSource(); 

     // asynchronically wait for next computer info 
     var compInfo = await GetComputerInfo(computerName, compCancelSource. Token); 
     // We are in UI context here 
     ShowOutputInGui(compInfo); 
     RunOnGuiThread(compInfo); 
    } 
} 

private Task<ComputerInfo> GetComputerInfo(String machineName, CancellationToken token) 
{ 
    var pingTime = Task.Factory.StartNew(
     // action to run 
     () => GetPingTime(machineName), 
     //token to cancel 
     token, 
     // notify the thread pool that this task could take a long time to run, 
     // so the new thread probably will be used for it 
     TaskCreationOptions.LongRunning, 
     // execute all the job in a thread pool 
     TaskScheduler.Default); 

    var processes = Task.Run(() => Process.GetProcesses(machineName), token, TaskCreationOptions.LongRunning, TaskScheduler.Default); 
    // and loads more 

    await Task.WhenAll(pingtime, processes, etc); 
    return new ComputerInfo(pingTime.Result, processes.Result, etc); 

    //var tcs = new TaskCompletionSource<ComputerInfo>(); 
    //Task.WhenAll(pingtime, processes, etc) 
    // .ContinueWith(aggregateTask => 
    //  if (aggregateTask.IsCompleted) 
    //  { 
    //   tcs.SetResult(new ComputerInfo(
    //    aggregateTask.Result[0], 
    //    aggregateTask.Result[1], 
    //    etc)); 
    //  } 
    //  else 
    //  { 
    //   // cancel or error handling 
    //  }); 

    // return the awaitable task 
    //return tcs.Task; 
} 
+0

Warum sollte 'GetComputerInfo' eine Fortsetzung und tcs verwenden, während' btn_Click' async ist? 'GetComputerInfo' sollte' Task 'zurückgeben. – Haukinger

+0

Dies ist ein asynchroner Wrapper, der TaskCompletionSource verwendet, siehe Abb. 9 aus dem verknüpften MSDN-Artikel. Unterschrift fixiert, danke. – VMAtm

+1

'Task.Run' ist der asynchrone Wrapper, aber' ContinueWith' ist ein Greuel, wenn der Rest des Codes 'async' /' await' verwendet. 'warten Task.WhenAll (...); return new ComputerInfo (...); 'wäre einfacher zu schreiben und einfacher zu lesen und zu verstehen. – Haukinger

Verwandte Themen