wake-up-neo.com

Aufgaben parallel ausführen

Ok, im Grunde habe ich eine Reihe von Aufgaben (10) und ich möchte sie alle gleichzeitig starten und warten, bis sie abgeschlossen sind. Wenn Sie fertig sind, möchte ich andere Aufgaben ausführen. Ich habe eine Reihe von Ressourcen darüber gelesen, kann es aber für meinen speziellen Fall nicht richtig machen ...

Folgendes habe ich momentan (Code wurde vereinfacht):

public async Task RunTasks()
{
    var tasks = new List<Task>
    {
        new Task(async () => await DoWork()),
        //and so on with the other 9 similar tasks
    }


    Parallel.ForEach(tasks, task =>
    {
        task.Start();
    });

    Task.WhenAll(tasks).ContinueWith(done =>
    {
        //Run the other tasks
    });
}

//This function perform some I/O operations
public async Task DoWork()
{
    var results = await GetDataFromDatabaseAsync();
    foreach (var result in results)
    {
        await ReadFromNetwork(result.Url);
    }
}

Mein Problem ist also, dass ich beim Warten auf Aufgaben mit dem Aufruf WhenAll mir sage, dass alle Aufgaben beendet sind, auch wenn keine erledigt ist. Ich habe versucht, Console.WriteLine in meiner foreach hinzuzufügen, und wenn ich die Folgeaufgabe eingegeben habe, werden immer Daten von meinen vorherigen Tasks eingehen, die nicht wirklich abgeschlossen sind.

Was mache ich hier falsch?

21
Yann Thibodeau

Sie sollten den Konstruktor Task fast nie direkt verwenden. In Ihrem Fall löst diese Aufgabe nur die eigentliche Aufgabe aus, auf die Sie nicht warten können.

Sie können einfach DoWork aufrufen und eine Aufgabe abrufen, in einer Liste speichern und warten, bis alle Aufgaben abgeschlossen sind. Bedeutung:

tasks.Add(DoWork());
// ...
await Task.WhenAll(tasks);

Async-Methoden werden jedoch synchron ausgeführt, bis das erste Warten auf eine nicht abgeschlossene Task erreicht ist. Wenn Sie sich Sorgen machen, dass dieser Teil zu lange dauert, verwenden Sie Task.Run, um ihn in einen anderen ThreadPool-Thread zu laden, und speichern Sie dann that task in der Liste:

tasks.Add(Task.Run(() => DoWork()));
// ...
await Task.WhenAll(tasks);
23
i3arnon

Im Wesentlichen mischen Sie zwei inkompatible asynchrone Paradigmen. d.h. Parallel.ForEach() und async-await.

Für das, was Sie wollen, tun Sie das eine oder das andere. Z.B. Sie können einfach Parallel.For[Each]() verwenden und das async-await vollständig löschen. Parallel.For[Each]() wird nur zurückgegeben, wenn alle parallelen Aufgaben abgeschlossen sind, und Sie können dann mit den anderen Aufgaben fortfahren.

Der Code hat auch einige andere Probleme:

  • sie markieren die Methode als asynchron, warten aber nicht darauf (die erwartete Erwartung ist der Delegierte, nicht die Methode);

  • sie möchten mit großer Wahrscheinlichkeit .ConfigureAwait(false) auf Ihre Erwartung, insbesondere wenn Sie nicht versuchen, die Ergebnisse sofort in einem UI-Thread zu verwenden.

8
sellotape

Wenn Sie diese Task in verschiedenen Threads mit TPL parallel ausführen möchten, benötigen Sie etwa Folgendes:

public async Task RunTasks()
{
    var tasks = new List<Func<Task>>
    {
       DoWork,
       //...
    };

    await Task.WhenAll(tasks.AsParallel().Select(async task => await task()));

    //Run the other tasks
}

Dabei wird nur eine kleine Menge an Code parallelisiert: Die Warteschlange der Methode in den Thread-Pool und die Rückgabe einer nicht abgeschlossenen Task. Auch für eine so kleine Taskmenge kann das Parallelisieren mehr Zeit erfordern, als nur asynchron zu laufen. Dies kann nur dann sinnvoll sein, wenn Ihre Aufgaben vor dem ersten Warten etwas länger (synchron) arbeiten.

In den meisten Fällen ist der bessere Weg:

public async Task RunTasks()
{
    await Task.WhenAll(new [] 
    {
        DoWork(),
        //...
    });
    //Run the other tasks
}

Zu meiner Meinung in Ihrem Code:

  1. Sie sollten Ihren Code nicht in Task einpacken, bevor Sie an Parallel.ForEach übergeben werden.

  2. Sie können nur awaitTask.WhenAll anstelle von ContinueWith verwenden.

2
Andrey Tretyak

Die DoWork-Methode ist eine asynchrone I/O-Methode. Das bedeutet, dass Sie nicht mehrere Threads benötigen, um mehrere von ihnen auszuführen, da die Methode meist asynchron auf den Abschluss der E/A wartet. Ein Thread genügt dafür.

public async Task RunTasks()
{
    var tasks = new List<Task>
    {
        DoWork(),
        //and so on with the other 9 similar tasks
    };

    await Task.WhenAll(tasks);

    //Run the other tasks            
}

Sie sollten fast niemals den Task-Konstruktor verwenden, um eine neue Aufgabe zu erstellen. Um eine asynchrone E/A-Aufgabe zu erstellen, rufen Sie einfach die async-Methode auf. Verwenden Sie Task.Run, um eine Aufgabe zu erstellen, die in einem Threadpool-Thread ausgeführt wird. In diesem Artikel finden Sie eine ausführliche Beschreibung von Task.Run und anderen Optionen zum Erstellen von Aufgaben.

1
Jakub Lortz

Fügen Sie einfach einen Try-Catch-Block um Task.WhenAll hinzu

Hinweis: Eine Instanz von System.AggregateException wird ausgelöst, die als Wrapper für eine oder mehrere aufgetretene Ausnahmen dient. Dies ist wichtig für Methoden, die mehrere Tasks wie Task.WaitAll () und Task.WaitAny () koordinieren, damit die AggregateException alle Ausnahmen innerhalb der ausgeführten Tasks umschließen kann.

try
{ 
    Task.WaitAll(tasks.ToArray());  
}
catch(AggregateException ex)
{ 
    foreach (Exception inner in ex.InnerExceptions)
    {
    Console.WriteLine(String.Format("Exception type {0} from {1}", inner.GetType(), inner.Source));
    }
}
0
NiTRiX-Reloaded