Laden...

Async/await innnerhalb eines LINQ Mappings

Erstellt von Papst vor 5 Jahren Letzter Beitrag vor 5 Jahren 1.650 Views
P
Papst Themenstarter:in
441 Beiträge seit 2014
vor 5 Jahren
Async/await innnerhalb eines LINQ Mappings

Hallo zusammen,

ich möchte gerne ein Objekt um weitere Informationen anreichern. Dafür verwendet ich ein Mapping mittels LINQ:


var t = item.Select(async x => new { x.Id, x.Value, Id2 = await service.GetAsync(x.id) });

Mein Problem gerade: Von welchem Typ ist t? --> t ist IEnumerable<Task<anonymous>>
Das ist etwas unpraktisch, denn wenn ich an die Datenobjekte herankommen möchte müsste ich


var result = Task.WhenAll(t);

machen.

Gibt es da nicht eine elegantere Lösung? SelectAsync() wäre schön 😄.
Auf Stackoverflow gibt es noch Vorschläge mit der .Result Property, das würde ich gerne vermeiden.

16.806 Beiträge seit 2008
vor 5 Jahren

Ich würde unter allen Umständen vermeiden in einer Selektion eine solche asynchrone Operation durchzuführen.

P
Papst Themenstarter:in
441 Beiträge seit 2014
vor 5 Jahren

Hmm, also den Service synchron programmieren oder mittels GetAwaiter().GetResult() ?

Ich wollte eigentlich gerne vermeiden synchrone Methoden in dem IO Belasteten Service anzubieten. Aktuell hängt da ein DB Call (ggf. mit Cache) dahinter, perspektivisch soll da ein HTTP Call zu einer API hinterhängen.

Würdest du den den Service so abändern, IEnumerable anstatt einer Id entgegennimmt und dann eine IDictionary zurückgibt, dass ich im Mapping verwenden kann?

6.911 Beiträge seit 2009
vor 5 Jahren

Hallo Papst,

Vorschläge mit der .Result Property...oder mittels GetAwaiter().GetResult() ?

Beides synchronisiert u.U. blockierend und sollte vermieden werden bzw. wäre es dann einfach gleich das synchrone Api aufzurufen.

Aktuell hängt da ein DB Call (ggf. mit Cache) dahinter, perspektivisch soll da ein HTTP Call zu einer API hinterhängen.

In dem skizzierten Fall würde für jedes Item eine asynchrone Operation ausgeführt werden. Das kann eine ganze Menge werden ("chatty") und ist eher suboptimal. Besser wäre wenn das Ganze "chunky" passiert, d.h. für alle / möglichst viele Items eine asynchrone Operation und dann das Ergebnis zu verarbeiten.
Das wäre so in der Art wie dein letzer Vorschlag.

Ausblick: mit C# 8 werden asynchrone Stream / Iteratoren möglich und ich gehe davon aus, dass es in der Folge auch eine Art "async Linq" geben wird.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

P
Papst Themenstarter:in
441 Beiträge seit 2014
vor 5 Jahren

Hi gfoidl,

du hast Recht, hier habe ich allerdings vorgesorgt. Der Service besteht aus zwei Services mit dem gleichen Interface, die sich gegenseitig aufrufen können. Der "vordere" fungiert dabei als Cache Proxy. Dieser hält die Informationen kurzzeitig vor, gerade weil ich Bursts dieser Anfragen erwarte und nicht auf die DB warten möchte.

Mir bleibt also die Wahl:
-> Frage alle Details der passenden Kategorie ab (fragt wesentlich mehr Daten als benötigt ab)
-> Frage explizit für die eingehenden Id's die "Id2" ab (im Bulk)
-> Frage async im Select ab

Architektonisch gefällt mir Option 1 am besten, ähnliche Abfragen habe ich schon, würde also der Linie treu bleiben.

6.911 Beiträge seit 2009
vor 5 Jahren

Hallo Papst,

mir gefällt Variante 2 am besten...außer die Details zur Kategorie sind eher statisch / ändern sich nicht zu oft und die Wahrscheinlichkeit dass die eingehenden Ids einen Großteil aller möglichen Kategorien abdecken und diese Daten auch hinreichtend oft benötigt werden, so gefällt mir auch Variante 1 besser, v.a. wenn diese zusammen mit einem Cache (z.B. Redis) kombiniert wird 😉 Klar?

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

16.806 Beiträge seit 2008
vor 5 Jahren

Soll das eine HTTP / Json API werden, bei der gewisse Dinge optional geladen werden können - wenn ja, kommt GraphQL infrage?
In der .NET Core Implementierung von GraphQL gibt es von Haus aus einen Mechanismus (als Teil des ObjectGraphType) für das optionale Laden.

Hier exemplarisch, dass "Address" bei der optionalen Angabe dynamisch geladen wird.

    public class UserProfileType : ObjectGraphType<UserProfile>
    {
        public UserProfileType(IMediator mediator)
        {
            Field(x => x.Id, type: typeof(IdGraphType)).Description("The id of the user.");
            Field(x => x.FirstName).Description("The first name of the user.");
            Field(x => x.LastName).Description("The last name of the user.");

            Field<UserAddressType, Address>()
                .Name("Address")
                .ResolveAsync(async ctx => await mediator.Send(new GetAddressOfUserQuery(cts.Source.UserId)))
                .Description("The address of the user.");

Im Endeffekt würde dies jedoch auf das von gfoidl angesprochene "chatty" hinaus laufen.
Mehr Daten als notwendig laden; das muss nicht unbedingt schlechter oder langsamer sein; gerade wenn Du hier von einer HTTP Schnittstelle sprichst, bei der i.d.R. der Body selbst den geringsten Teil der Zeit einer Anfrage ausmacht - daher würde ich aufgrund von KISS und premature optimization zu Variante 1 Deinre Auflistung tendieren.

Die meiste Zeit gegen eine handelsübliche HTTP API ist leider immer noch der Verbindungsaufbau und die Response-Time selbst; nicht die Erzeugung oder Übertragung des Inhalts.
Im dümmsten Fall kostet Dein Mechanismus also mehr Zeit unterm Strich.

W
955 Beiträge seit 2010
vor 5 Jahren

Würdest du den den Service so abändern, IEnumerable anstatt einer Id entgegennimmt und dann eine IDictionary zurückgibt, dass ich im Mapping verwenden kann? Wenn dieser Service dann in einem Rutsch die Daten anfordern kann, also nicht für jede id eine extra Anfrage/Verbindung mach muss wäre das wohl die einfachste Lösung.

709 Beiträge seit 2008
vor 5 Jahren

Als Lösung zum Select-Problem gäbe es auch noch die AsyncUtilities (siehe TaskEnumerableAwaiter).

P
Papst Themenstarter:in
441 Beiträge seit 2014
vor 5 Jahren

Hi,

danke für eure Meinungen. Ich werde wohl auf die Bulk Abfrage gehen, also:


var ids = input.Data.Select(x => x.Id);
Dictionary<Guid, Guid> idMap = await service.GetAllAsync(ids);

@Abt: Noch ist es keine HTTP Api, sondern ein EFCore Context, der im zweiten Schritt abgefragt wird.

6.911 Beiträge seit 2009
vor 5 Jahren

Hallo pinki,

Als Lösung zum Select-Problem gäbe es auch noch die
>
(siehe TaskEnumerableAwaiter).

Nein, bitte nicht -- das ist auch nur ein schön verpacktest Task.WhenAll und bringt somit nichts an Verbesserung.

Wenn schon so in der Art, dann eher wie es in Throttling -- Begrenzung der max. Anzahl an gleichzeitig ausgeführten asynchronen Vorgängen umgesetzt ist, da hier "async durchgezogen" wird, ohne dass irgendwo blockierend auf ein Ergebnis gewartet werden muss.

async ist "viral" und sollte nie mit einem blockierenden Warten* verbunden werden.

* die einzige Ausnahme ist die Main-Methode, die ab C# 7 asynchron geschrieben werden kann und dort der Compiler zu GetAwaiter().GetResult() umschreibt, somit blockiert, aber hier macht es auch Sinn...

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"