c# when Verwenden Sie async/warten auf mehrere Aufgaben




when to use async c# (4)

Ich verwende einen API-Client, der vollständig asynchron ist, dh jede Operation gibt entweder Task oder Task<T> , zB:

static async Task DoSomething(int siteId, int postId, IBlogClient client)
{
    await client.DeletePost(siteId, postId); // call API client
    Console.WriteLine("Deleted post {0}.", siteId);
}

Unter Verwendung der C # 5 async / await-Operatoren können Sie mit der richtigen / effizientesten Methode mehrere Aufgaben starten und warten, bis alle ausgeführt wurden:

int[] ids = new[] { 1, 2, 3, 4, 5 };
Parallel.ForEach(ids, i => DoSomething(1, i, blogClient).Wait());

oder:

int[] ids = new[] { 1, 2, 3, 4, 5 };
Task.WaitAll(ids.Select(i => DoSomething(1, i, blogClient)).ToArray());

Da der API-Client HttpClient intern verwendet, würde ich davon ausgehen, dass 5 HTTP-Anforderungen sofort ausgegeben werden, sobald sie auf die Konsole geschrieben werden.


Ich war neugierig, die Ergebnisse der Methoden in der Frage sowie die akzeptierte Antwort zu sehen, also habe ich es auf die Probe gestellt.

Hier ist der Code:

class Program
{
    class Worker
    {
        public int Id { get; set; }
        public int SleepTimeout { get; set; }

        public async Task DoWork()
        {
            Console.WriteLine("Worker {0} started on thread {1} at {2}.",
                Id, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("hh:mm:ss.fff"));
            await Task.Run(() => Thread.Sleep(SleepTimeout));
            Console.WriteLine("Worker {0} stopped at {1}.",
                Id, DateTime.Now.ToString("hh:mm:ss.fff"));
        }
    }

    static void Main(string[] args)
    {
        var workers = new List<Worker>
        {
            new Worker { Id = 1, SleepTimeout = 3000 },
            new Worker { Id = 2, SleepTimeout = 3000 },
            new Worker { Id = 3, SleepTimeout = 3000 },
            new Worker { Id = 4, SleepTimeout = 3000 },
            new Worker { Id = 5, SleepTimeout = 3000 },
        };

        Console.WriteLine("Starting test: Parallel.ForEach");
        PerformTest_ParallelForEach(workers);
        Console.WriteLine("Test finished.\n");

        Console.WriteLine("Starting test: Task.WaitAll");
        PerformTest_TaskWaitAll(workers);
        Console.WriteLine("Test finished.\n");

        Console.WriteLine("Starting test: Task.WhenAll");
        var task = PerformTest_TaskWhenAll(workers);
        task.Wait();
        Console.WriteLine("Test finished.\n");

        Console.ReadKey();
    }

    static void PerformTest_ParallelForEach(List<Worker> workers)
    {
        Parallel.ForEach(workers, worker => worker.DoWork().Wait());
    }

    static void PerformTest_TaskWaitAll(List<Worker> workers)
    {
        Task.WaitAll(workers.Select(worker => worker.DoWork()).ToArray());
    }

    static Task PerformTest_TaskWhenAll(List<Worker> workers)
    {
        return Task.WhenAll(workers.Select(worker => worker.DoWork()));
    }
}

Und die resultierende Ausgabe:


Sie können die Task.WhenAll Funktion verwenden, mit der Sie n Tasks übergeben können. Task.WhenAll gibt eine Aufgabe zurück, die Task.WhenAll wird, wenn alle Aufgaben, die Sie an Task.WhenAll abgeschlossen sind. Sie müssen asynchron auf Task.WhenAll warten, damit Sie den Task.WhenAll nicht blockieren:

   public async Task DoSomeThing() {

       var Task[] tasks = new Task[numTasks];
       for(int i = 0; i < numTask; i++)
       {
          tasks[i] = CallSomeAsync();
       }
       await Task.WhenAll(tasks);
       // code that'll execute on UI thread
   }

int[] ids = new[] { 1, 2, 3, 4, 5 };
Parallel.ForEach(ids, i => DoSomething(1, i, blogClient).Wait());

Obwohl Sie die Operationen parallel zu dem obigen Code ausführen, blockiert dieser Code jeden Thread, auf dem jede Operation ausgeführt wird. Wenn der Netzwerkanruf beispielsweise 2 Sekunden dauert, bleibt jeder Thread 2 Sekunden lang stehen, ohne etwas zu tun.

int[] ids = new[] { 1, 2, 3, 4, 5 };
Task.WaitAll(ids.Select(i => DoSomething(1, i, blogClient)).ToArray());

Auf der anderen Seite blockiert der obige Code mit WaitAll auch die Threads und Ihre Threads sind nicht frei, um andere Arbeit zu verarbeiten, bis die Operation endet.

Empfohlener Ansatz

Ich würde WhenAll bevorzugen, die Ihre Operationen asynchron in Parallel durchführen wird.

public async Task DoWork() {

    int[] ids = new[] { 1, 2, 3, 4, 5 };
    await Task.WhenAll(ids.Select(i => DoSomething(1, i, blogClient)));
}

Tatsächlich müssen Sie im obigen Fall nicht einmal await , Sie können einfach direkt von der Methode zurückkehren, da Sie keine Fortsetzungen haben:

public Task DoWork() 
{
    int[] ids = new[] { 1, 2, 3, 4, 5 };
    return Task.WhenAll(ids.Select(i => DoSomething(1, i, blogClient)));
}

Um dies zu untermauern, hier ist ein ausführlicher Blogbeitrag, der alle Alternativen und ihre Vorteile / Nachteile durchgeht: Wie und wo gleichzeitige asynchrone E / A mit ASP.NET Web API


Da die von Ihnen aufgerufene API asynchron ist, macht die Parallel.ForEach Version wenig Sinn. Sie sollten nicht. .Wait in der WaitAll Version verwenden, da dies die Parallelität verlieren würde. Eine andere Alternative, wenn der Aufrufer async ist, verwendet Task.WhenAll nach dem Ausführen von Select und ToArray , um das Array von Aufgaben zu generieren. Eine zweite Alternative ist die Verwendung von Rx 2.0





c#-5.0