Laden...

Threads async machen

Erstellt von DNS46 vor einem Jahr Letzter Beitrag vor einem Jahr 564 Views
D
DNS46 Themenstarter:in
21 Beiträge seit 2020
vor einem Jahr
Threads async machen

Huhu,

kann mir hier jemand nen Hinweis geben? Ich möchte in dem Mini Beispiel parallele Pings starten, warten bis alle ihr Ergebnis geliefert haben und diese anschließend gesammelt ausprinten.
Jetzt fällt aber auf, dass die Aufrufe von Call(..) gar nicht parallel ausgeführt werden, sondern aufeinander warten. Was hab ich denn nicht beachtet?
Ja und dann kau ich gerade am Schlüsselwort await. Erfüllt WaitAll() hier genau den gleichen Zweck?


static void Main(string[] args)
{
   List<Task<string>> myTasks = new List<Task<string>>();
   foreach (string url in new string[] { "mycsharp.de", "www.google.de", "www.wolframalpha.com" })
   {
       myTasks.Add(Task.Run(() => Call(url)));
   }
   Task.WaitAll(myTasks.ToArray());
   foreach (Task<string> task in myTasks)
   {
       Console.WriteLine(task.Result);
   }
}

static async Task<string> Call(string url)
{          
   Ping ping = new Ping();
   PingReply reply = ping.Send(url, 1000);
   return String.Format("IP: {0}\t=> time: {1}ms\t(url: {2})", reply.Address,reply.RoundtripTime, url);
}

// result:
// IP: 13.107.219.45       => time: 22ms   (url: mycsharp.de)
// IP: 216.239.38.120      => time: 23ms   (url: www.google.de)
// IP: 140.177.50.10       => time: 124ms  (url: www.wolframalpha.com)

Danke euch 🙂

T
2.219 Beiträge seit 2008
vor einem Jahr

Kleiner Tip, du kannst mit SendPingAsync den Aufruf direkt mit einem eigenen Task starten.
Dann kann der Rest mit async/await gelöst werden.

Nachtrag:
Du kannst dann auch gleich Console.WriteLine in der async Methode ausführen.
Dann musst du nicht erst den String bauen und hin und her schieben nur für die Ausgabe.

Nachtrag 2:
Die Umsetzung könnte man auch mit Parallel.Foreach umsetzen.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

2.078 Beiträge seit 2012
vor einem Jahr

Lies nach, was der Unterschied von "parallel" und "asynchron" bedeutet - ist nämlich nicht das gleiche 😉
Dein Ziel kannst Du mit der Parallel-Klasse erreichen, die ist genau für sowas da.

Außerdem ist dein Code nirgendwo asynchron.
So, wie das geschrieben ist, führst Du die synchron arbeitende Call-Methode (ja, die arbeitet synchron, sollte dir VisualStudio auch mitteilen) jeweils in einem eigenen ThreadPool-Thread aus.

Jetzt fällt aber auf, dass die Aufrufe von Call(..) gar nicht parallel ausgeführt werden, sondern aufeinander warten.

Was lässt dich das glauben? Die Zeit, die da steht, ist die Zeit, die der Ping braucht und hat nichts mit den Tasks zu tun.
Der Code wird nacheinander gestartet, läuft also schon, wenn Du den Task der Liste hinzufügst
Der Code wird dann jeweils in einem eigenen Thread ausgeführt und unabhängig voneinander ausgeführt.

Ja und dann kau ich gerade am Schlüsselwort await. Erfüllt WaitAll() hier genau den gleichen Zweck?

Nein.
WaitAll() wartet synchron, bis alle Task fertig sind - in vielen Fällen eine blöde Idee.
WhenAll() ist das asynchrone Gegenstück dazu und sollte bevorzugt werden.

Auf einen Task wartet man mit await, aber dann ist es nicht mehr "parallel" sondern "asynchron".

"Richtiger" asynchron sähe es so aus:


static async Task Main(string[] args)
{
    List<Task<string>> myTasks = new List<Task<string>>();

    foreach (string url in new string[] { "mycsharp.de", "www.google.de", "www.wolframalpha.com" })
    {
        myTasks.Add(CallAsync(url));
    }

    foreach (Task<string> task in myTasks)
    {
        Console.WriteLine(await task);
    }
}

static async Task<string> CallAsync(string url)
{
    Ping ping = new Ping();
    PingReply reply = await ping.SendPingAsync(url, 1000);
    return String.Format("IP: {0}\t=> time: {1}ms\t(url: {2})", reply.Address,reply.RoundtripTime, url);
}

Oder, wie T-Virus vorschlägt:


static async Task Main(string[] args)
{
    List<Task> myTasks = new List<Task>();

    foreach (string url in new string[] { "mycsharp.de", "www.google.de", "www.wolframalpha.com" })
    {
        myTasks.Add(CallAsync(url));
    }

    await Task.WhenAll(myTasks);
}

static async Task CallAsync(string url)
{
    Ping ping = new Ping();
    PingReply reply = await ping.SendPingAsync(url, 1000);
    var message = String.Format("IP: {0}\t=> time: {1}ms\t(url: {2})", reply.Address,reply.RoundtripTime, url);
    Console.WriteLine(message);
}

Keine Garantie, dass es funktioniert, wurde im Browser geschrieben, aber das Prinzip wird denke ich klar.

Damit werden die Tasks in genau der Reihenfolge aus der Liste erwartet und das Ergebnis ausgegeben.
Aber das heißt nicht, dass der Code in der Call-Methode auch genau in der Reihenfolge ausgeführt wird, er wird immer noch in der ersten Schleife gestartet.
Es könnte z.B. sein, dass alle drei Pings nacheinander gestartet werden, der zweite Eintrag ist zuerst fertig, aber auf den ersten Eintrag wird zuerst gewartet, der zweite Eintrag ist dann aber schon fertig und es muss nicht darauf gewartet werden.

du kannst mit SendAsync den Aufruf direkt mit einem eigenen Task starten.

Die Methode ist nicht "wirklich" asynchron, sie gibt keinen Task zurück, sondern arbeitet stattdessen mit dem PingCompleted-Event.
Die "richtig" asynchrone Methode ist "SendPingAsync".

T
2.219 Beiträge seit 2008
vor einem Jahr

@Palladin007
Hatte den Aufruf von SendAsync oben schon in SendPingAsync geändert 🙂
Aber danke nochmal für den Hinweis 🙂

Hier mein dotnet Fiddle dazu:
https://dotnetfiddle.net/GeoCcU

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

16.806 Beiträge seit 2008
vor einem Jahr

Und hier das Beispiel von T-Virus in einer etwas zuweisungsfreundlicheren, asynchronem Prozess-Einstieg und Ohne-Fiddle-Link Schreibweise 🙂


using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
using System.Threading.Tasks;

public class Program
{
    static async Task Main(string[] args)
    {
        string[] addresses = new[] { "mycsharp.de", "www.google.de", "www.wolframalpha.com" };

        IEnumerable<Task> tasks = addresses.Select(address => PingAsync(address));

        await Task.WhenAll(tasks);
    }

    private static async Task PingAsync(string url)
    {
        PingReply reply;
        using (Ping ping = new())
        {
            reply = await ping.SendPingAsync(url, 1000);
        }
        Console.WriteLine("IP: {0}\t=> time: {1}ms\t(url: {2})", reply.Address, reply.RoundtripTime, url);
    }
}

PS: für produktiven Code ConfigureAwait beachten.
ConfigureAwait FAQ

D
DNS46 Themenstarter:in
21 Beiträge seit 2020
vor einem Jahr

Ja jetzt ist es so wie ich es mir vorgstellt hatte. Glaub meine größte Frage ist von je her wie ihr es immer schafft so schnell zu Antworten

 IP: 13.107.219.45       => time: 22ms   (url: mycsharp.de) 

ist echt quick 🙂
Ping.Send hatte ich nur als Bsp. gewählt um ein paar Milliseconds verstreichen zu lassen. Da die für await gar nicht verwendbar ist bedeutet das ja, dass gar nicht jede Methode asynchron ausgeführt werden kann 😐... nur wenn die über die GetAwaiter Extension verfügt. Damit kenn ich mich noch nicht aus
Danke für die Tipps .... seid die Besten 🙂