Chiamare il metodo asincrono in modo sincrono


231

Ho un asyncmetodo:

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

Devo chiamare questo metodo da un metodo sincrono.

Come posso fare questo senza dover duplicare il GenerateCodeAsyncmetodo affinché questo funzioni in modo sincrono?

Aggiornare

Tuttavia non è stata trovata alcuna soluzione ragionevole.

Tuttavia, vedo che HttpClientimplementa già questo modello

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

    // sync
    HttpResponseMessage responseSync = client.GetAsync(url).Result;
}


1
Speravo in una soluzione più semplice, pensando che asp.net gestisse molto più facilmente che scrivere così tante righe di codice
Catalin

Perché non abbracciare semplicemente il codice asincrono? Idealmente, avresti bisogno di più codice asincrono, non di meno.
Paulo Morgado,

54
[Perché non limitarsi ad abbracciare il codice asincrono?] Ah, potrebbe essere proprio perché si sta abbracciando il codice asincrono che hanno bisogno di questa soluzione poiché gran parte del progetto viene convertita! Non puoi ricostruire Roma in un giorno.
Nicholas Petersen,

1
@NicholasPetersen a volte una libreria di terze parti può costringerti a farlo. Esempio di creazione di messaggi dinamici nel metodo WithMessage su FluentValidation. Non esiste un'API asincrona per questo a causa del design della libreria - i sovraccarichi WithMessage sono statici. Altri metodi per passare argomenti dinamici a WithMessage sono strani.
H.Wojtowicz,

Risposte:


278

Puoi accedere alla Resultproprietà dell'attività, che causerà il blocco del thread fino a quando il risultato non sarà disponibile:

string code = GenerateCodeAsync().Result;

Nota: in alcuni casi, ciò potrebbe portare a un deadlock: la chiamata per Resultbloccare il thread principale, impedendo così l'esecuzione del resto del codice asincrono. Hai le seguenti opzioni per assicurarti che ciò non accada:

Questo non significa che dovresti semplicemente aggiungere senza pensarci .ConfigureAwait(false)dopo tutte le tue chiamate asincrone! Per un'analisi dettagliata del perché e del momento in cui utilizzare .ConfigureAwait(false), consultare il seguente post di blog:


33
Se invocare resultrischia un deadlock, allora quando è sicuro ottenere il risultato? Ogni chiamata asincrona richiede Task.Runo ConfigureAwait(false)?
Robert Harvey,

4
Non esiste un "thread principale" in ASP.NET (a differenza di un'app GUI), ma il deadlock è ancora possibile a causa di come AspNetSynchronizationContext.Post serializza le continuazioni asincrone:Task newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action)); _lastScheduledTask = newTask;
noseratio,

4
@RobertHarvey: se non hai alcun controllo sull'implementazione del metodo asincrono su cui stai bloccando, quindi sì, dovresti avvolgerlo Task.Runper stare al sicuro. Oppure usa qualcosa di simile WithNoContextper ridurre la commutazione del thread ridondante.
noseratio,

10
NOTA: la chiamata .Resultpuò comunque essere bloccata se il chiamante si trova nel pool di thread stesso. Prendi uno scenario in cui il pool di thread ha dimensioni 32 e 32 attività sono in esecuzione e in Wait()/Resultattesa su un'attività 33a non ancora programmata che desidera essere eseguita su uno dei thread in attesa.
Warty,

55

Dovresti ottenere il cameriere ( GetAwaiter()) e terminare l'attesa per il completamento dell'attività asincrona ( GetResult()).

string code = GenerateCodeAsync().GetAwaiter().GetResult();

38
Ci siamo imbattuti in deadlock usando questa soluzione. Essere avvisati
Oliver,

6
MSDNTask.GetAwaiter : questo metodo è destinato all'uso del compilatore anziché all'uso nel codice dell'applicazione.
foka,

Ho ancora visualizzato il messaggio di errore Finestra di dialogo (contro la mia volontà), con i pulsanti "Passa a" o "Riprova"…. tuttavia, la chiamata viene effettivamente eseguita e ritorna con una risposta adeguata.
Jonathan Hansen,

30

Dovresti riuscire a farlo usando i delegati, espressione lambda

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" ;
    }

Questo frammento non verrà compilato. Il tipo restituito da Task.Run è Task. Vedi questo blog MSDN per una spiegazione completa.
Appetere,

5
Grazie per la segnalazione, sì, restituisce il tipo di attività. La sostituzione di "string sCode" nell'attività <string> o var sCode dovrebbe risolverlo. Aggiunta di un codice di compilazione completo per facilità.
Faiyaz,

20

Devo chiamare questo metodo da un metodo sincrono.

È possibile con GenerateCodeAsync().Resulto GenerateCodeAsync().Wait(), come suggerisce l'altra risposta. Ciò bloccherebbe il thread corrente fino al GenerateCodeAsynccompletamento.

Tuttavia, la tua domanda è taggata con e hai anche lasciato il commento:

Speravo in una soluzione più semplice, pensando che asp.net lo gestisse molto più facilmente che scrivere così tante righe di codice

Il punto è che non dovresti bloccare un metodo asincrono in ASP.NET. Ciò ridurrà la scalabilità della tua app Web e potrebbe creare un deadlock (quando viene pubblicata una awaitcontinuazione all'interno ). L'uso per scaricare qualcosa su un thread del pool e quindi il blocco danneggerà ulteriormente la scalabilità, poiché comporta +1 thread in più per elaborare una determinata richiesta HTTP.GenerateCodeAsyncAspNetSynchronizationContextTask.Run(...).Result

ASP.NET ha il supporto integrato per i metodi asincroni, tramite controller asincroni (in ASP.NET MVC e API Web) o direttamente tramite AsyncManagere PageAsyncTasknel classico ASP.NET. Dovresti usarlo. Per maggiori dettagli, controlla questa risposta .


Sto sovrascrivendo il SaveChanges()metodo di DbContext, e qui sto chiamando i metodi asincroni, quindi sfortunatamente il controller asincrono non mi aiuterà in questa situazione
Catalin

3
@RaraituL, in generale, non mescoli il codice asincrono e sincronizza, scegli il modello euther. Puoi implementare entrambi SaveChangesAsynce SaveChanges, assicurati solo che non vengano chiamati entrambi nello stesso progetto ASP.NET.
noseratio,

4
Ad .NET MVCesempio IAuthorizationFilter, non tutti i filtri supportano il codice asincrono , quindi non posso utilizzarlo asyncfino in fondo
Catalin

3
@Noseratio che è un obiettivo non realistico. Esistono troppe librerie con codice asincrono e sincrono, nonché situazioni in cui non è possibile utilizzare un solo modello. I filtri di azione MVC, ad esempio, non supportano il codice asincrono.
Justin Skiles,

9
@Noserato, la domanda riguarda la chiamata del metodo asincrono da sincrono. A volte non puoi cambiare l'API che stai implementando. Supponiamo che tu stia implementando un'interfaccia sincrona da un framework di terze parti "A" (non puoi riscrivere il framework in modo asincrono) ma la libreria di terze parti "B" che stai tentando di usare nella tua implementazione ha solo asincrono. Anche il prodotto risultante è anche una libreria e può essere utilizzato ovunque incluso ASP.NET ecc.
dimzon

19

Microsoft Identity ha metodi di estensione che chiamano i metodi asincroni in modo sincrono. Ad esempio c'è il metodo GenerateUserIdentityAsync () e uguale CreateIdentity ()

Se guardi UserManagerExtensions.CreateIdentity () è simile al seguente:

 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));
    }

Ora vediamo cosa fa AsyncHelper.RunSync

  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();
    }

Quindi, questo è il wrapper per il metodo asincrono. E per favore non leggere i dati dal risultato - potenzialmente bloccherà il tuo codice in ASP.

C'è un altro modo - che è sospetto per me, ma puoi anche considerarlo

  Result r = null;

            YourAsyncMethod()
                .ContinueWith(t =>
                {
                    r = t.Result;
                })
                .Wait();

3
Quale ritieni sia il problema con il secondo modo che hai suggerito?
David Clarke, il

@DavidClarke probabilmente il problema di sicurezza del thread relativo all'accesso a una variabile non volatile da più thread senza blocco.
Theodor Zoulias,

9

Per evitare deadlock, cerco sempre di usare Task.Run()quando devo chiamare un metodo asincrono in modo sincrono che menziona @Heinzi.

Tuttavia, il metodo deve essere modificato se il metodo asincrono utilizza parametri. Ad esempio Task.Run(GenerateCodeAsync("test")).Resultdà l'errore:

Argomento 1: impossibile convertire da ' System.Threading.Tasks.Task<string>' a 'System.Action'

Questo potrebbe invece essere chiamato così:

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

6

La maggior parte delle risposte su questo thread sono complesse o causeranno deadlock.

Il seguente metodo è semplice ed eviterà il deadlock perché stiamo aspettando che l'attività finisca e solo allora ottenga il suo risultato-

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

Inoltre, ecco un riferimento all'articolo MSDN che parla esattamente della stessa cosa- https://blogs.msdn.microsoft.com/jpsanders/2017/08/28/asp-net-do-not-use-task-result- in-main-contesto /


1

Preferisco un approccio non bloccante:

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

0

Bene sto usando questo approccio:

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

        return task.Result;
    }

-1

L'altro modo potrebbe essere se si desidera attendere fino al termine dell'attività:

var t = GenerateCodeService.GenerateCodeAsync();
Task.WhenAll(t);
string code = t.Result;

1
Questo è chiaramente sbagliato. WhenAll restituisce anche un'attività che non si sta aspettando.
Robert Schmidt,

-1

MODIFICARE:

L'attività ha il metodo Wait, Task.Wait (), che attende che la "promessa" si risolva e poi continua, rendendola così sincrona. esempio:


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

String mySyncMethod() {

    return MyAsyncMethod().Wait();
}

3
Si prega di elaborare la risposta. Come si usa In che modo aiuta a rispondere alla domanda?
Scratte

-2

Se hai un metodo asincrono chiamato " RefreshList ", puoi chiamare quel metodo asincrono da un metodo non asincrono come di seguito.

Task.Run(async () => { await RefreshList(); });
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.