it-swarm.com.de

Async in linq select warten

Ich muss ein vorhandenes Programm ändern und es enthält folgenden Code:

var inputs = events.Select(async ev => await ProcessEventAsync(ev))
                   .Select(t => t.Result)
                   .Where(i => i != null)
                   .ToList();

Dies erscheint mir jedoch sehr komisch, vor allem die Verwendung von async und await im select. Laut dieser Antwort von Stephen Cleary sollte ich diese fallen lassen können. 

Dann der zweite Select, der das Ergebnis auswählt. Bedeutet das nicht, dass die Aufgabe überhaupt nicht asynchron ist und synchron ausgeführt wird (so viel Aufwand für nichts), oder wird die Aufgabe asynchron ausgeführt, und wenn sie erledigt ist, wird der Rest der Abfrage ausgeführt.

Soll ich den obigen Code wie folgt schreiben: eine andere Antwort von Stephen Cleary :

var tasks = await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev)));
var inputs = tasks.Where(result => result != null).ToList();

und ist es ganz dasselbe?

var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))))
                                       .Where(result => result != null).ToList();

Während ich an diesem Projekt arbeite, würde ich gerne das erste Codebeispiel ändern, aber ich bin nicht besonders daran interessiert, asynchronen Code zu ändern. Vielleicht mache ich mir nur Sorgen um nichts und alle 3 Codebeispiele machen genau das gleiche?

ProcessEventsAsync sieht folgendermaßen aus:

async Task<InputResult> ProcessEventAsync(InputEvent ev) {...}
97
Alexander Derck
var inputs = events.Select(async ev => await ProcessEventAsync(ev))
                   .Select(t => t.Result)
                   .Where(i => i != null)
                   .ToList();

Dies erscheint mir jedoch sehr komisch, vor allem die Verwendung von asynchron und abwarten im select. Laut dieser Antwort von Stephen Cleary sollte ich sie fallen lassen können.

Der Aufruf von Select ist gültig. Diese beiden Zeilen sind im Wesentlichen identisch:

events.Select(async ev => await ProcessEventAsync(ev))
events.Select(ev => ProcessEventAsync(ev))

(Es gibt einen geringfügigen Unterschied hinsichtlich der Art und Weise, wie eine synchrone Ausnahme von ProcessEventAsync ausgelöst wird, aber im Zusammenhang mit diesem Code spielt es keine Rolle.)

Dann die zweite Auswahl, die das Ergebnis auswählt. Bedeutet das nicht, dass die Aufgabe überhaupt nicht asynchron ist und synchron ausgeführt wird (so viel Aufwand für nichts), oder wird die Aufgabe asynchron ausgeführt, und wenn sie erledigt ist, wird der Rest der Abfrage ausgeführt.

Dies bedeutet, dass die Abfrage blockiert. Es ist also nicht wirklich asynchron.

Brechen sie ab:

var inputs = events.Select(async ev => await ProcessEventAsync(ev))

startet zunächst für jedes Ereignis einen asynchronen Vorgang. Dann diese Zeile:

                   .Select(t => t.Result)

wartet darauf, dass diese Vorgänge einzeln ausgeführt werden (zuerst wartet der Vorgang auf das erste Ereignis, dann das nächste, dann das nächste usw.).

Dies ist der Teil, für den ich mich nicht interessiere, da er alle Ausnahmen in AggregateException blockiert und auch einschließen würde.

und ist es ganz dasselbe?

var tasks = await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev)));
var inputs = tasks.Where(result => result != null).ToList();

var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))))
                                       .Where(result => result != null).ToList();

Ja, diese beiden Beispiele sind gleichwertig. Beide starten alle asynchronen Vorgänge (events.Select(...)), warten dann asynchron, bis alle Vorgänge in beliebiger Reihenfolge abgeschlossen sind (await Task.WhenAll(...)), und fahren dann mit dem Rest der Arbeit (Where...) fort.

Beide Beispiele unterscheiden sich vom ursprünglichen Code. Der ursprüngliche Code blockiert die Ausnahmen in AggregateException.

105
Stephen Cleary

Bestehender Code funktioniert, blockiert jedoch den Thread.

.Select(async ev => await ProcessEventAsync(ev))

erstellt für jedes Ereignis eine neue Aufgabe, aber

.Select(t => t.Result)

blockiert den Thread, der auf das Ende jeder neuen Aufgabe wartet.

Andererseits erzeugt Ihr Code dasselbe Ergebnis, bleibt aber asynchron.

Nur ein Kommentar zu Ihrem ersten Code. Diese Linie

var tasks = await Task.WhenAll(events...

erzeugt eine einzelne Task, daher sollte die Variable in Singular benannt werden.

Schließlich macht Ihr letzter Code dasselbe, ist aber knapper 

Für Referenz: Task.Wait / Task.WhenAll

16
tede24

Mit den aktuellen Methoden, die in Linq verfügbar sind, sieht es ziemlich hässlich aus:

var tasks = items.Select(
    async item => new
    {
        Item = item,
        IsValid = await IsValid(item)
    });
var tuples = await Task.WhenAll(tasks);
var validItems = tuples
    .Where(p => p.IsValid)
    .Select(p => p.Item)
    .ToList();

Die folgenden .NET-Versionen werden hoffentlich mit eleganteren Werkzeugen für die Sammlung von Aufgaben und Aufgaben von Sammlungen aufwarten.

6

Ich bevorzuge dies als Erweiterungsmethode:

public static async Task<IEnumerable<T>> WhenAll<T>(this IEnumerable<Task<T>> tasks)
{
    return await Task.WhenAll(tasks);
}

Damit es mit Method Chaining verwendbar ist:

var inputs = await events
  .Select(async ev => await ProcessEventAsync(ev))
  .WhenAll()
2
Daryl

Ich habe diesen Code verwendet:

public static async Task<IEnumerable<TResult>> SelectAsync<TSource,TResult>(this IEnumerable<TSource> source, Func<TSource, Task<TResult>> method)
{
      return await Task.WhenAll(source.Select(async s => await method(s)));
}

so was:

var result = await sourceEnumerable.SelectAsync(async s=>await someFunction(s,other params));
0