Laden...

Event aus asyncroner Methode

Erstellt von ill_son vor 2 Jahren Letzter Beitrag vor 2 Jahren 279 Views
I
ill_son Themenstarter:in
227 Beiträge seit 2009
vor 2 Jahren
Event aus asyncroner Methode

Hallo,

folgendes Problem bekomme ich gerade nicht gelöst:

Ich habe eine Klasse, welche mir über VCP an den Rechner angeschlossene Sensoren sucht. Diese hat folgende Methode


public async Task ScanAsync(IProgress<NewSensor> progressIndicator)
{
    if (!IsBusy)
    {
        IEnumerable<string> portNames = _ComPortWatcher.GetCurrentPortNames();
        await ScanPortsForSensors(portNames, progressIndicator);
    }
}

Diese starte ich aus meinem VM mit einem AsyncCommand -> alles gut.

Nun soll aber der Scan auch ausgelöst werden, wenn neue Com-Ports auftauchen. Dafür gibt es eine Klasse, die auf entsprechende RegistryEvents reagiert und einen Scan anstößt.


private async void ComPortWatcher_PortsAdded(object sender, SerialCommEventArgs e)
{
    await ScanPortsForSensors(e.PortNames, null); 
}

Nun meine Frage: Wie bekomme ich die detektierten Sensoren gemeldet? Wenn ich es richtig verstanden habe kehrt ja der await-Aufruf sofort zurück (fire and forget), da ComPortWatcher_PortsAdded async void ist und nicht Task zurück gibt, was bei Eventhandlern ja legitim zu sein scheint, wie ich gelesen habe. Kann ich da im Anschluss ein Event auslösen?

Grüße, Alex

Final no hay nada más

2.078 Beiträge seit 2012
vor 2 Jahren

Das await ist im Hintergrund etwas anders, als Du denkst.
In deinem Fall wird das await vermutlich alles darauf Folgende in den UI-Thread zur Bearbeitung geben.
Das await kehrt dabei nicht fire-and-forget zurück! Dass kein Task zurückgegeben wird, heißt nur, dass der Methodenaufrufer nicht auf den Task warten kann, aber das ist bei Events legitim.

Das heißt, Du kannst nach dem Await normal ein Event aufrufen.

309 Beiträge seit 2020
vor 2 Jahren

Soll deine zweite Klasse dann das Event auslösen? (Also nicht ComPortWatcher?)

Wenn ja, füg ein Delegate hinzu, definier dein Event und abonniere es dann wo es nutzen willst.
In die PortsAdded-Methode kommt dann das Invoke (wenn ich dein Vorhaben richtig verstanden habe)

I
ill_son Themenstarter:in
227 Beiträge seit 2009
vor 2 Jahren

Hallo,

danke für eure Antworten.

@JimShark: So habe ich es im Augenblick auch gemacht. Ich habe es noch ein bisschen umgeschrieben, um den Progress auch nur dort zu benutzen, wo ich ihn brauche.


private async Task<IEnumerable<NewSensor>> ScanPortsForSensorsAsync(IEnumerable<string> portNames)
{
    // scan goes here
}

private async void ComPortWatcher_PortsAdded(object sender, SerialCommEventArgs e)
{
    IEnumerable<NewSensor> newSensors = await ScanPortsForSensorsAsync(e.PortNames);
    foreach (NewSensor sensor in newSensors)
    {
        NewSensorDetected?.Invoke(this, new NewSensorEventArgs(sensor));
    }
}

Der SensorScanner aboniert das PortsAdded-Event des ComPortWatchers und löst daraufhin einen Scan aus. Die Frage war nur, ob das so einfach geht. Ich bin noch einbisschen neu in der Thematik async Task und alles was ich immer gelesen habe, war: Task mit Progress verwenden. Neulich hatte ich ein interessantes Tutorial, wo erklärt wurde, dass async void wie fire-and-forget zu betrachten ist. Momentan läuft es auch noch nicht zuverlässig.

Final no hay nada más

2.078 Beiträge seit 2012
vor 2 Jahren

Führe die Events doch direkt in der Methode aus, oder gibt es einen konkreten Grund, warum Du das nicht machst?
Die Methode ist nicht nur wegen des awaits automatisch in einem neuen Thread, für die Methode gilt intern das gleiche, wie für das Event.
Den neuen Thread hast Du erst dann, wenn Du auch tatsächlich einen neuen Task auf machst, der dann einen neuen Thread bekommt.

Mach dir Mal in VisualStudio eine Watch auf auf die aktuelle ThreadID oder schreib dir ThreadID ins Debug-Output und verfolge sie.

Task mit Progress verwenden

Das Progress-System ist nur ein Konzept, Status-Änderungen abstrahiert an die UI weiterzugeben.
Du musst kein Progress nutzen, aber Du kannst. Nutzen würde ich es nur dann, wenn ich es auch brauche.

dass async void wie fire-and-forget zu betrachten ist

Der Aufruf von einer Methode mit async void ist wie fire-and-forget, die Methode selber nicht.


// EventHandler sind seltene Ausnahmen, wo async void in Ordnung ist:
async void OnMyEvent(object sender, EventArgs e)
{
    // UI-Thread
    await DoWorkAsync();
    // DoWorkAsync ist zuende
    // UI-Thread
}

async Task DoWorkAsync()
{
    // UI-Thread
    await Task.Delay(100);
    // Task.Delay ist zuende
    // UI-Thread
}

// Fast immer schlecht:
async void DoAsyncVoidWork()
{
    // UI-Thread
    await DoWorkAsync();
    // DoWorkAsync ist zuende
    // UI-Thread
}

void DoOtherWork()
{
    // UI-Thread
    DoAsyncVoidWork(); // Fire & Forget
    // await DoAsyncVoidWork(); // Compile-fehler
    // DoWorkAsync ist NICHT zuende
    // UI-Thread
}

Du solltest dich ausführlich mit Tasks beschäftigen.
Für kleine Projekte ok, solange Fehler nicht so schlimm sein, aber im produktiven Umfeld können die schnell zu schlimmen Problemen werden.
Die Einstiegshürde ist leider ziemlich groß, dafür machen sie einige Probleme bedeutend leichter, wenn man weiß, was man tut.

16.807 Beiträge seit 2008
vor 2 Jahren

Neulich hatte ich ein interessantes Tutorial, wo erklärt wurde, dass async void wie fire-and-forget zu betrachten ist.

Konzeptionell ist dem so - kommt aber auf den Kontext an.
In diesem Fall vermute ich, dass Du es falsch verstehst.

async void führt dazu, dass der Aufrufer keinerlei Informationen über die Aktion erhält; eben keinen Task.
Aus Sicht des Aufrufers ist damit dieser Aufruf "Fire and Forget".

Inhaltlich ist das aber kein Fire and Forget, denn in der Methode selbst existiert die State Machine und wird auch beachtet.

Ein echtes technologisches Fire and Forget geht entweder über Task.Run (die bequeme Methode) bzw. absolut korrekt über ThreadPool.UnsafeQueueUserWorkItem()

PS: ein Task sollte kein IEnumerable zurück geben.
Entweder Du arbeitest mit materialisierten Listen wie List<T> oder Du nimmst IAsyncEnumerable
IEnumerable kann unerwünschte Seiteneffekte hervorrufen und ist selbst auch überhaupt nicht asynchron.

PPS: da Du eine asynchrone UI offenbar umsetzen willst, solltest Du Dir mal Reactive Extensions anschauen.
Damit lassen sich 99% der UI Szenarien viel einfacher umsetzen, hast es sauber asynchron, kannst Bindings verwenden und musst Dir über nervige Events keine sorgen machen, weil Du einfach Subscriptions zur Aktualisierung der Inhalte verwenden kannst.