Asynchrone Methode synchron aufrufen

230

Ich habe eine asyncMethode:

public async Task<string> GenerateCodeAsync()
{
    string code = await GenerateCodeService.GenerateCodeAsync();
    return code;
}

Ich muss diese Methode von einer synchronen Methode aus aufrufen.

Wie kann ich dies tun, ohne die GenerateCodeAsyncMethode duplizieren zu müssen, damit dies synchron funktioniert?

Aktualisieren

Es wurde jedoch keine vernünftige Lösung gefunden.

Ich sehe jedoch, dass HttpClientdieses Muster bereits implementiert ist

using (HttpClient client = new HttpClient())
{
    // async
    HttpResponseMessage responseAsync = await client.GetAsync(url);

    // sync
    HttpResponseMessage responseSync = client.GetAsync(url).Result;
}
Catalin
quelle
1
Ich hatte auf eine einfachere Lösung gehofft und dachte, dass asp.net dies viel einfacher handhabte als so viele Codezeilen zu schreiben
Catalin
Warum nicht einfach asynchronen Code annehmen? Idealerweise möchten Sie mehr asynchronen Code, nicht weniger.
Paulo Morgado
53
[Warum nicht einfach asynchronen Code annehmen?] Ha, gerade weil man asynchronen Code akzeptiert, benötigen sie diese Lösung, wenn große Teile des Projekts konvertiert werden! Sie können Rom nicht an einem Tag wieder aufbauen.
Nicholas Petersen
1
@NicholasPetersen Manchmal kann eine Bibliothek eines Drittanbieters Sie dazu zwingen. Beispiel für das Erstellen dynammischer Nachrichten in der WithMessage-Methode aus FluentValidation. Aufgrund des Bibliotheksdesigns gibt es hierfür keine asynchrone API. WithMessage-Überladungen sind statisch. Andere Methoden zum Übergeben dynamischer Argumente an WithMessage sind seltsam.
H.Wojtowicz

Antworten:

278

Sie können auf die ResultEigenschaft der Aufgabe zugreifen , wodurch Ihr Thread blockiert wird, bis das Ergebnis verfügbar ist:

string code = GenerateCodeAsync().Result;

Hinweis: In einigen Fällen kann dies zu einem Deadlock führen: Ihr Aufruf zum ResultBlockieren des Hauptthreads verhindert, dass der Rest des asynchronen Codes ausgeführt wird. Sie haben folgende Möglichkeiten, um sicherzustellen, dass dies nicht geschieht:

Dies bedeutet nicht , dass Sie .ConfigureAwait(false)nach all Ihren asynchronen Aufrufen nur sinnlos hinzufügen sollten ! Eine detaillierte Analyse, warum und wann Sie verwenden sollten .ConfigureAwait(false), finden Sie im folgenden Blog-Beitrag:

Heinzi
quelle
33
Wenn das Aufrufen die resultGefahr eines Deadlocks birgt, wann ist es dann sicher, das Ergebnis zu erhalten? Benötigt jeder asynchrone Aufruf Task.Runoder ConfigureAwait(false)?
Robert Harvey
4
Es gibt keinen „Haupt - Thread“ in ASP.NET ( im Gegensatz zu einer GUI - Anwendung), aber die Blockade ist nach wie vor möglich , weil , wie AspNetSynchronizationContext.Post serialisiert async Fortsetzungen:Task newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action)); _lastScheduledTask = newTask;
noseratio
4
@RobertHarvey: Wenn Sie keine Kontrolle über die Implementierung der asynchronen Methode haben, die Sie blockieren, sollten Sie sie einschließen, um die Sicherheit zu gewährleisten Task.Run. Oder verwenden Sie so etwas wie WithNoContext, um redundantes Thread-Switching zu reduzieren.
Noseratio
10
ANMERKUNG: Der Anruf .Resultkann immer noch blockieren, wenn sich der Anrufer im Thread-Pool selbst befindet. Stellen Sie sich ein Szenario vor, in dem der Thread-Pool die Größe 32 hat und 32 Aufgaben ausgeführt werden und Wait()/Resultauf eine noch zu planende 33. Aufgabe warten, die auf einem der wartenden Threads ausgeführt werden soll.
Warty
55

Sie sollten den awaiter ( GetAwaiter()) erhalten und das Warten auf den Abschluss der asynchronen Task ( GetResult()) beenden .

string code = GenerateCodeAsync().GetAwaiter().GetResult();
Diego Torres
quelle
37
Mit dieser Lösung sind wir auf Deadlocks gestoßen. Sei gewarnt.
Oliver
6
MSDNTask.GetAwaiter : Diese Methode ist eher für die Verwendung durch den Compiler als für die Verwendung im Anwendungscode vorgesehen.
Foka
Ich habe immer noch das Fehler-Dialog-Popup (gegen meinen Willen) mit den Schaltflächen 'Wechseln zu' oder 'Wiederholen'…. Der Aufruf wird jedoch tatsächlich ausgeführt und mit einer ordnungsgemäßen Antwort zurückgegeben.
Jonathan Hansen
30

Sie sollten in der Lage sein, dies mit Delegaten, Lambda-Ausdruck, zu erledigen

private void button2_Click(object sender, EventArgs e)
    {

        label1.Text = "waiting....";

        Task<string> sCode = Task.Run(async () =>
        {
            string msg =await GenerateCodeAsync();
            return msg;
        });

        label1.Text += sCode.Result;

    }

    private Task<string> GenerateCodeAsync()
    {
        return Task.Run<string>(() => GenerateCode());
    }

    private string GenerateCode()
    {
        Thread.Sleep(2000);
        return "I m back" ;
    }
Faiyaz
quelle
Dieses Snippet wird nicht kompiliert. Der Rückgabetyp von Task.Run ist Task. In diesem MSDN-Blog finden Sie eine vollständige Erklärung.
Appetere
5
Vielen Dank für den Hinweis, ja, es wird der Aufgabentyp zurückgegeben. Das Ersetzen von "string sCode" durch Task <string> oder var sCode sollte das Problem beheben. Hinzufügen eines vollständigen Kompilierungscodes zur Vereinfachung.
Faiyaz
20

Ich muss diese Methode von einer synchronen Methode aus aufrufen.

Es ist möglich mit GenerateCodeAsync().Resultoder GenerateCodeAsync().Wait(), wie die andere Antwort nahelegt. Dies würde den aktuellen Thread blockieren, bis er GenerateCodeAsyncabgeschlossen ist.

Ihre Frage ist jedoch mit markiert und du hast auch den Kommentar hinterlassen:

Ich hatte auf eine einfachere Lösung gehofft und dachte, dass asp.net dies viel einfacher handhabt als so viele Codezeilen zu schreiben

Mein Punkt ist, dass Sie eine asynchrone Methode in ASP.NET nicht blockieren sollten . Dies verringert die Skalierbarkeit Ihrer Web-App und kann zu einem Deadlock führen (wenn eine awaitFortsetzung im Innern von GenerateCodeAsyncveröffentlicht wird AspNetSynchronizationContext). Wenn Sie Task.Run(...).Resultetwas in einen Pool-Thread auslagern und dann blockieren, wird die Skalierbarkeit noch mehr beeinträchtigt, da +1 mehr Thread für die Verarbeitung einer bestimmten HTTP-Anforderung erforderlich ist.

ASP.NET bietet integrierte Unterstützung für asynchrone Methoden, entweder über asynchrone Controller (in ASP.NET MVC und Web API) oder direkt über AsyncManagerund PageAsyncTaskin klassischem ASP.NET. Du solltest es benutzen. Weitere Informationen finden Sie in dieser Antwort .

noseratio
quelle
Ich überschreibe die SaveChanges()Methode von DbContext, und hier rufe ich die asynchronen Methoden auf, so dass mir der asynchrone Controller in dieser Situation leider nicht hilft
Catalin
3
@RaraituL, im Allgemeinen mischen Sie keinen Async- und Sync-Code, sondern wählen ein anderes Modell aus. Sie können beide implementieren SaveChangesAsyncund SaveChangesnur sicherstellen, dass sie nicht beide im selben ASP.NET-Projekt aufgerufen werden.
Noseratio
4
Nicht alle .NET MVCFilter unterstützen asynchronen Code, zum Beispiel IAuthorizationFilter, so dass ich nicht verwenden können , den asyncganzen Weg
Catalin
3
@Noseratio das ist ein unrealistisches Ziel. Es gibt zu viele Bibliotheken mit asynchronem und synchronem Code sowie Situationen, in denen die Verwendung nur eines Modells nicht möglich ist. MVC ActionFilters unterstützen beispielsweise keinen asynchronen Code.
Justin Skiles
9
@Noserato, die Frage betrifft das Aufrufen der asynchronen Methode von synchron. Manchmal können Sie die von Ihnen implementierte API nicht ändern. Angenommen, Sie implementieren eine synchrone Schnittstelle von einem Drittanbieter-Framework "A" (Sie können das Framework nicht asynchron umschreiben), aber die Drittanbieter-Bibliothek "B", die Sie in Ihrer Implementierung verwenden möchten, ist nur asynchron. Das resultierende Produkt ist auch eine Bibliothek und kann überall verwendet werden, einschließlich ASP.NET usw.
dimzon
19

Microsoft Identity verfügt über Erweiterungsmethoden, die asynchrone Methoden synchron aufrufen. Zum Beispiel gibt es die GenerateUserIdentityAsync () -Methode und die gleiche CreateIdentity ()

Wenn Sie sich UserManagerExtensions.CreateIdentity () ansehen, sieht es folgendermaßen aus:

 public static ClaimsIdentity CreateIdentity<TUser, TKey>(this UserManager<TUser, TKey> manager, TUser user,
        string authenticationType)
        where TKey : IEquatable<TKey>
        where TUser : class, IUser<TKey>
    {
        if (manager == null)
        {
            throw new ArgumentNullException("manager");
        }
        return AsyncHelper.RunSync(() => manager.CreateIdentityAsync(user, authenticationType));
    }

Nun wollen wir sehen, was AsyncHelper.RunSync macht

  public static TResult RunSync<TResult>(Func<Task<TResult>> func)
    {
        var cultureUi = CultureInfo.CurrentUICulture;
        var culture = CultureInfo.CurrentCulture;
        return _myTaskFactory.StartNew(() =>
        {
            Thread.CurrentThread.CurrentCulture = culture;
            Thread.CurrentThread.CurrentUICulture = cultureUi;
            return func();
        }).Unwrap().GetAwaiter().GetResult();
    }

Dies ist also Ihr Wrapper für die asynchrone Methode. Und bitte lesen Sie keine Daten aus dem Ergebnis - es wird möglicherweise Ihren Code in ASP blockieren.

Es gibt einen anderen Weg - der für mich verdächtig ist, aber Sie können ihn auch in Betracht ziehen

  Result r = null;

            YourAsyncMethod()
                .ContinueWith(t =>
                {
                    r = t.Result;
                })
                .Wait();
Vitaliy Markitanov
quelle
3
Was ist Ihrer Meinung nach das Problem mit dem von Ihnen vorgeschlagenen zweiten Weg?
David Clarke
@DavidClarke ist wahrscheinlich das Thread-Sicherheitsproblem beim Zugriff auf eine nichtflüchtige Variable von mehreren Threads ohne Sperre.
Theodor Zoulias
9

Um Deadlocks zu vermeiden, versuche ich immer, diese zu verwenden, Task.Run()wenn ich eine von @Heinzi erwähnte asynchrone Methode synchron aufrufen muss.

Die Methode muss jedoch geändert werden, wenn die asynchrone Methode Parameter verwendet. Zum Beispiel Task.Run(GenerateCodeAsync("test")).Resultgibt der Fehler:

Argument 1: Konvertierung von ' System.Threading.Tasks.Task<string>' nach 'System.Action' nicht möglich

Dies könnte stattdessen so genannt werden:

string code = Task.Run(() => GenerateCodeAsync("test")).Result;
Ogglas
quelle
5

Die meisten Antworten in diesem Thread sind entweder komplex oder führen zu einem Deadlock.

Die folgende Methode ist einfach und vermeidet Deadlocks, da wir darauf warten, dass die Aufgabe abgeschlossen ist und erst dann das Ergebnis erhält.

var task = Task.Run(() => GenerateCodeAsync()); 
task.Wait();
string code = task.Result;

Darüber hinaus finden Sie hier einen Verweis auf einen MSDN-Artikel, der genau dasselbe behandelt: https://blogs.msdn.microsoft.com/jpsanders/2017/08/28/asp-net-do-not-use-task-result- im Hauptkontext /

Kamalpreet
quelle
0

Ich bevorzuge einen nicht blockierenden Ansatz:

            Dim aw1=GenerateCodeAsync().GetAwaiter()
            While Not aw1.IsCompleted
                Application.DoEvents()
            End While
Zibri
quelle
0

Nun, ich benutze diesen Ansatz:

    private string RunSync()
    {
        var task = Task.Run(async () => await GenerateCodeService.GenerateCodeAsync());
        if (task.IsFaulted && task.Exception != null)
        {
            throw task.Exception;
        }

        return task.Result;
    }
Jiří Herník
quelle
-1

Der andere Weg könnte sein, wenn Sie warten möchten, bis die Aufgabe abgeschlossen ist:

var t = GenerateCodeService.GenerateCodeAsync();
Task.WhenAll(t);
string code = t.Result;
Frablaser
quelle
1
Das ist einfach falsch. WhenAll gibt auch eine Aufgabe zurück, auf die Sie nicht warten.
Robert Schmidt
-1

BEARBEITEN:

Task verfügt über die Wait-Methode Task.Wait (), die darauf wartet, dass das "Versprechen" aufgelöst wird, und dann fortfährt, wodurch es synchronisiert wird. Beispiel:


async Task<String> MyAsyncMethod() { ... }

String mySyncMethod() {

    return MyAsyncMethod().Wait();
}
Avi Tshuva
quelle
3
Bitte erläutern Sie Ihre Antwort. Wie wird es benutzt? Wie konkret hilft es, die Frage zu beantworten?
Scratte
-2

Wenn Sie eine asynchrone Methode namens " RefreshList " haben, können Sie diese asynchrone Methode von einer nicht asynchronen Methode wie unten aufrufen.

Task.Run(async () => { await RefreshList(); });
dush88c
quelle