Come chiamare il metodo asincrono dal metodo sincrono in C #?


863

Ho un public async void Foo()metodo che voglio chiamare dal metodo sincrono. Finora tutto ciò che ho visto dalla documentazione di MSDN è chiamare metodi asincroni tramite metodi asincroni, ma il mio intero programma non è costruito con metodi asincroni.

È possibile?

Ecco un esempio di come chiamare questi metodi da un metodo asincrono: http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx

Ora sto cercando di chiamare questi metodi asincroni dai metodi di sincronizzazione.


2
Mi sono imbattuto anche in questo. Sovrascrivendo un RoleProvider non è possibile modificare la firma del metodo del metodo GetRolesForUser, quindi non è possibile rendere il metodo asincrono e quindi non è possibile utilizzare wait per richiamare api in modo asincrono. La mia soluzione temporanea era quella di aggiungere metodi sincroni alla mia classe HttpClient generica, ma vorrei sapere se questo è possibile (e quali potrebbero essere le implicazioni).
Timothy Lee Russell,

1
Poiché il async void Foo()metodo non restituisce un Tasksignifica che un chiamante non può sapere quando viene completato, deve Taskinvece restituire .
Dai

1
Collegamento di una q / a correlata su come eseguire questa operazione in un thread dell'interfaccia utente.
noseratio,

Risposte:


711

La programmazione asincrona "cresce" attraverso la base di codice. È stato paragonato a un virus zombi . La soluzione migliore è permettergli di crescere, ma a volte ciò non è possibile.

Ho scritto alcuni tipi nella mia libreria Nito.AsyncEx per gestire una base di codice parzialmente asincrona. Non c'è soluzione che funzioni in ogni situazione, però.

Soluzione A

Se hai un metodo asincrono semplice che non ha bisogno di risincronizzarsi con il suo contesto, puoi usare Task.WaitAndUnwrapException:

var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();

Non si desidera utilizzare Task.Waito Task.Resultperché racchiudono eccezioni AggregateException.

Questa soluzione è appropriata solo se MyAsyncMethodnon si sincronizza nuovamente con il suo contesto. In altre parole, ogni awaitin MyAsyncMethoddovrebbe finire con ConfigureAwait(false). Ciò significa che non è possibile aggiornare alcun elemento dell'interfaccia utente o accedere al contesto della richiesta ASP.NET.

Soluzione B

Se è MyAsyncMethodnecessario sincronizzare di nuovo con il suo contesto, è possibile che sia possibile utilizzare AsyncContext.RunTaskper fornire un contesto nidificato:

var result = AsyncContext.RunTask(MyAsyncMethod).Result;

* Aggiornamento 14/04/2014: nelle versioni più recenti della libreria l'API è la seguente:

var result = AsyncContext.Run(MyAsyncMethod);

(Va bene usare Task.Resultin questo esempio perché RunTaskpropagherà le Taskeccezioni).

Il motivo che potrebbe essere necessario AsyncContext.RunTaskinvece Task.WaitAndUnwrapExceptionè a causa di una possibilità di deadlock piuttosto sottile che si verifica su WinForms / WPF / SL / ASP.NET:

  1. Un metodo sincrono chiama un metodo asincrono, ottenendo un Task.
  2. Il metodo sincrono attende un blocco sul Task.
  3. Il asyncmetodo utilizza awaitsenza ConfigureAwait.
  4. Non è Taskpossibile completare in questa situazione perché si completa solo al termine del asyncmetodo; il asyncmetodo non può essere completato perché sta tentando di pianificare la sua continuazione su SynchronizationContexte WinForms / WPF / SL / ASP.NET non consentirà l'esecuzione della continuazione perché il metodo sincrono è già in esecuzione in quel contesto.

Questo è uno dei motivi per cui è una buona idea usare il più possibile ConfigureAwait(false)in ogni asyncmetodo.

Soluzione C

AsyncContext.RunTasknon funzionerà in tutti gli scenari. Ad esempio, se il asyncmetodo attende qualcosa che richiede il completamento di un evento dell'interfaccia utente, il deadlock verrà eseguito anche con il contesto nidificato. In tal caso, è possibile avviare il asyncmetodo sul pool di thread:

var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();

Tuttavia, questa soluzione richiede MyAsyncMethodche funzionerà nel contesto del pool di thread. Quindi non può aggiornare gli elementi dell'interfaccia utente o accedere al contesto della richiesta ASP.NET. E in tal caso, puoi anche aggiungere ConfigureAwait(false)alle sue awaitdichiarazioni e utilizzare la soluzione A.

Aggiornamento, 01/05/2019: le attuali "pratiche peggiori" sono contenute in un articolo MSDN qui .


9
La soluzione A sembra ciò che voglio, ma sembra task.WaitAndUnwrapException () non è arrivato a .Net 4.5 RC; ha solo task.Wait (). Qualche idea su come farlo con la nuova versione? O è un metodo di estensione personalizzato che hai scritto?
cane mortale

3
WaitAndUnwrapExceptionè il mio metodo dalla mia libreria AsyncEx . Le librerie .NET ufficiali non forniscono molto aiuto per mescolare codice di sincronizzazione e asincrono (e in generale, non dovresti farlo!). Sto aspettando .NET 4.5 RTW e un nuovo laptop non XP prima di aggiornare AsyncEx su 4.5 (al momento non riesco a svilupparlo per 4.5 perché sono bloccato su XP per qualche settimana in più).
Stephen Cleary,

12
AsyncContextora ha un Runmetodo che accetta un'espressione lambda, quindi dovresti usarevar result = AsyncContext.Run(() => MyAsyncMethod());
Stephen Cleary il

1
Ho rimosso la tua libreria da Nuget, ma in realtà non sembra avere un RunTaskmetodo. La cosa più vicina che potevo trovare era Run, ma che non ha una Resultproprietà.
Asad Saeeduddin,


313

Aggiungendo una soluzione che alla fine ha risolto il mio problema, si spera risparmi il tempo di qualcuno.

Per prima cosa leggi un paio di articoli di Stephen Cleary :

Dalle "due migliori pratiche" in "Non bloccare il codice asincrono", la prima non ha funzionato per me e la seconda non era applicabile (in pratica, se posso usare await, lo faccio!).

Quindi ecco la mia soluzione alternativa: avvolgere la chiamata all'interno di un Task.Run<>(async () => await FunctionAsync());e speriamo non ci sia più un deadlock .

Ecco il mio codice:

public class LogReader
{
    ILogger _logger;

    public LogReader(ILogger logger)
    {
        _logger = logger;
    }

    public LogEntity GetLog()
    {
        Task<LogEntity> task = Task.Run<LogEntity>(async () => await GetLogAsync());
        return task.Result;
    }

    public async Task<LogEntity> GetLogAsync()
    {
        var result = await _logger.GetAsync();
        // more code here...
        return result as LogEntity;
    }
}

5
Due anni dopo, sono curioso di sapere come questa soluzione regge. Qualche notizia? C'è sottigliezza in questo approccio che si perde nei neofiti?
Dan Esparza,

26
Questo non sarà deadlock, vero, ma semplicemente perché è costretto a essere eseguito in un nuovo thread, al di fuori del contesto di sincronizzazione del thread di origine. Tuttavia, ci sono alcuni ambienti in cui questo è molto sconsigliato: in particolare le applicazioni web. Ciò potrebbe dimezzare efficacemente i thread disponibili per il server Web (un thread per la richiesta e uno per questo). Più lo fai, peggio diventa. Potresti potenzialmente finire con il deadlock dell'intero server web.
Chris Pratt,

30
@ChrisPratt - Forse hai ragione, perché Task.Run()non è una buona pratica in un codice asincrono. Ma, di nuovo, qual è la risposta alla domanda originale? Non chiamare mai un metodo asincrono in modo sincrono? Desideriamo, ma in un mondo reale, a volte dobbiamo.
Tohid

1
@Perché potresti provare la libreria di Stephen Cleary. Ho visto la gente supporre questo e l' Parallel.ForEachabuso non avrà alcun effetto nel "mondo reale" e alla fine ha eliminato i server. Questo codice è OK per le app della console, ma come dice @ChrisPratt, non dovrebbe essere usato nelle app Web. Potrebbe funzionare "adesso" ma non è scalabile.
makhdumi,

1
Sono incuriosito di iniziare a creare nuovi account in SO rispondendo alle domande solo per ottenere abbastanza punti per votare questo ....
Giannis Paraskevopoulos,

206

Microsoft ha creato una classe AsyncHelper (interna) per eseguire Async come sincronizzazione. La fonte assomiglia a:

internal static class AsyncHelper
{
    private static readonly TaskFactory _myTaskFactory = new 
      TaskFactory(CancellationToken.None, 
                  TaskCreationOptions.None, 
                  TaskContinuationOptions.None, 
                  TaskScheduler.Default);

    public static TResult RunSync<TResult>(Func<Task<TResult>> func)
    {
        return AsyncHelper._myTaskFactory
          .StartNew<Task<TResult>>(func)
          .Unwrap<TResult>()
          .GetAwaiter()
          .GetResult();
    }

    public static void RunSync(Func<Task> func)
    {
        AsyncHelper._myTaskFactory
          .StartNew<Task>(func)
          .Unwrap()
          .GetAwaiter()
          .GetResult();
    }
}

Le classi base Microsoft.AspNet.Identity hanno solo metodi Async e per chiamarli come Sync ci sono classi con metodi di estensione che sembrano (esempio di utilizzo):

public static TUser FindById<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
    if (manager == null)
    {
        throw new ArgumentNullException("manager");
    }
    return AsyncHelper.RunSync<TUser>(() => manager.FindByIdAsync(userId));
}

public static bool IsInRole<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId, string role) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
    if (manager == null)
    {
        throw new ArgumentNullException("manager");
    }
    return AsyncHelper.RunSync<bool>(() => manager.IsInRoleAsync(userId, role));
}

Per coloro che sono preoccupati per i termini di licenza del codice, ecco un link a un codice molto simile (aggiunge solo il supporto per la cultura sul thread) che ha commenti per indicare che è concesso in licenza da MIT da Microsoft. https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs


2
I miei metodi asincroni attendono altri metodi asincroni. Non decoro nessuna delle mie awaitchiamate con ConfigureAwait(false). Ho provato a usare AsyncHelper.RunSyncper chiamare una funzione asincrona dalla Application_Start()funzione in Global.asax e sembra funzionare. Questo significa che non AsyncHelper.RunSyncè affidabile incline al problema di deadlock "torna al contesto del chiamante" di cui ho letto altrove in questo post?
Bob.at.Indigo.Salute

1
@ Bob.at.SBS dipende da cosa fai il codice. Non è così semplice come se dovessi usare questo codice, sono sicuro . Questo è un modo molto minimale e semi-sicuro per eseguire i comandi asincroni in modo sincrono, può essere facilmente usato in modo inappropriato per causare deadlock.
Erik Philips,

1
Grazie. 2 domande di follow-up: 1) Puoi dare un esempio di qualcosa che il metodo asincrono vuole evitare che causerebbe un deadlock, e 2) i deadlock in questo contesto sono spesso dipendenti dal tempo? Se funziona in pratica, potrei ancora avere un deadlock dipendente dal tempo in agguato nel mio codice?
Bob.at.Indigo.Salute

@ Bob.at.SBS Consiglierei di porre una domanda utilizzando il pulsante Poni domanda in alto a destra. Puoi includere un link a questa domanda o rispondere alla tua domanda come riferimento.
Erik Philips,

1
@ Bob.at ... il codice fornito da Erik funziona perfettamente sotto Asp. net mvc5 ed EF6, ma non quando ho provato una delle altre soluzioni (ConfigureAwait (false) .GetAwaiter (). GetResult () o .result) che blocca completamente la mia app Web
LeonardoX,

151

async Main ora fa parte di C # 7.2 e può essere abilitato nelle impostazioni di build avanzate dei progetti.

Per C # <7.2, il modo corretto è:

static void Main(string[] args)
{
   MainAsync().GetAwaiter().GetResult();
}


static async Task MainAsync()
{
   /*await stuff here*/
}

Lo vedrai usato in molta documentazione Microsoft, ad esempio: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use- temi-abbonamenti


11
Non ho idea del perché qualcuno abbia votato in questo senso. Questo ha funzionato alla grande per me. Senza questa correzione, avrei dovuto propagare ASYCH OVUNQUE.
Prigioniero ZERO,

11
Perché è meglio di MainAsync().Wait()?
schiaccia il

8
Sono d'accordo. Hai solo bisogno di MainAsync (). Wait () invece di tutto questo.
Hajjat,

8
@crush Stavo descrivendo come questo può evitare alcuni deadlock. In alcune situazioni la chiamata a .Wait () da un'interfaccia utente o thread asp.net provoca un deadlock. deadlock asincroni
David

6
@ClintB: non dovresti assolutamente farlo in ASP.NET Core. Le applicazioni Web sono particolarmente vulnerabili all'essere affamate di thread e ogni volta che lo fai, stai estraendo un thread dal pool che altrimenti verrebbe utilizzato per servire una richiesta. È meno problematico per le applicazioni desktop / mobili perché sono tradizionalmente monoutente.
Chris Pratt,

52
public async Task<string> StartMyTask()
{
    await Foo()
    // code to execute once foo is done
}

static void Main()
{
     var myTask = StartMyTask(); // call your method which will return control once it hits await
     // now you can continue executing code here
     string result = myTask.Result; // wait for the task to complete to continue
     // use result

}

Leggi la parola chiave "wait" come "avvia questa attività a esecuzione prolungata, quindi restituisci il controllo al metodo chiamante". Una volta eseguita l'attività di lunga durata, esegue il codice dopo di essa. Il codice dopo l'attesa è simile a quelli che erano i metodi CallBack. La grande differenza è che il flusso logico non viene interrotto, il che rende molto più facile scrivere e leggere.


15
Waitavvolge le eccezioni e ha la possibilità di un deadlock.
Stephen Cleary,

Ho pensato che se avessi chiamato un metodo asincrono senza usare await, sarebbe stato eseguito in modo sincrono. Almeno questo funziona per me (senza chiamare myTask.Wait). In realtà, ho ricevuto un'eccezione quando ho provato a chiamare myTask.RunSynchronously()perché era già stato eseguito!
timore l'

2
Mi piace questa risposta. Buoni commenti per l'editing, piccoli ed eleganti. Grazie per aver contribuito! Sto ancora imparando la concorrenza, quindi tutto aiuta :)
kayleeFrye_onDeck

2
Questa risposta dovrebbe funzionare ancora oggi? L'ho appena provato in un progetto MVC Razor e l'app si blocca solo sull'accesso .Result.
Codice andato finito il

8
@TrueBlueAussie Questo è il deadlock del contesto di sincronizzazione. Il tuo codice asincrono torna al contesto di sincronizzazione, ma al momento è bloccato dalla Resultchiamata, quindi non arriva mai lì. E Resultnon finisce mai, perché sta aspettando qualcuno che sta aspettando la Resultfine, in sostanza: D
Luaan,

40

Non sono sicuro al 100%, ma credo che la tecnica descritta in questo blog dovrebbe funzionare in molte circostanze:

È quindi possibile utilizzare task.GetAwaiter().GetResult()se si desidera invocare direttamente questa logica di propagazione.


6
La soluzione A nella risposta di Stephen Cleary sopra utilizza questo metodo. Vedi la fonte WaitAndUnwrapException .
orad

hai bisogno di utilizzare GetResult () se la funzione che stai chiamando è nulla o attività? Voglio dire, se non vuoi ottenere alcun risultato indietro
batmaci,

Sì, altrimenti non si bloccherà fino al completamento dell'attività. In alternativa, invece di chiamare GetAwaiter (). GetResult () è possibile chiamare .Wait ()
NStuke

1
Questa è la parte "molte circostanze". Dipende dal modello di threading generale e da cosa stanno facendo gli altri thread per determinare se esiste il rischio di deadlock o meno.
NStuke,

GetAwaiter (). GetResult () può ancora causare deadlock. Svolge l'eccezione solo in una più sensata.
nawfal

25

Esiste, tuttavia, una buona soluzione che funziona in (quasi: leggi i commenti) in ogni situazione: un pump dei messaggi ad hoc (SynchronizationContext).

Il thread chiamante verrà bloccato come previsto, pur garantendo che tutte le continuazioni chiamate dalla funzione asincrona non si blocchino poiché verranno trasferite su SynchronizationContext (pump dei messaggi) ad hoc in esecuzione sul thread chiamante.

Il codice dell'helper pump dei messaggi ad hoc:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Threading
{
    /// <summary>Provides a pump that supports running asynchronous methods on the current thread.</summary>
    public static class AsyncPump
    {
        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static void Run(Action asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(true);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function
                syncCtx.OperationStarted();
                asyncMethod();
                syncCtx.OperationCompleted();

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static void Run(Func<Task> asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(false);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function and alert the context to when it completes
                var t = asyncMethod();
                if (t == null) throw new InvalidOperationException("No task provided.");
                t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
                t.GetAwaiter().GetResult();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static T Run<T>(Func<Task<T>> asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(false);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function and alert the context to when it completes
                var t = asyncMethod();
                if (t == null) throw new InvalidOperationException("No task provided.");
                t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
                return t.GetAwaiter().GetResult();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Provides a SynchronizationContext that's single-threaded.</summary>
        private sealed class SingleThreadSynchronizationContext : SynchronizationContext
        {
            /// <summary>The queue of work items.</summary>
            private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> m_queue =
                new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
            /// <summary>The processing thread.</summary>
            private readonly Thread m_thread = Thread.CurrentThread;
            /// <summary>The number of outstanding operations.</summary>
            private int m_operationCount = 0;
            /// <summary>Whether to track operations m_operationCount.</summary>
            private readonly bool m_trackOperations;

            /// <summary>Initializes the context.</summary>
            /// <param name="trackOperations">Whether to track operation count.</param>
            internal SingleThreadSynchronizationContext(bool trackOperations)
            {
                m_trackOperations = trackOperations;
            }

            /// <summary>Dispatches an asynchronous message to the synchronization context.</summary>
            /// <param name="d">The System.Threading.SendOrPostCallback delegate to call.</param>
            /// <param name="state">The object passed to the delegate.</param>
            public override void Post(SendOrPostCallback d, object state)
            {
                if (d == null) throw new ArgumentNullException("d");
                m_queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
            }

            /// <summary>Not supported.</summary>
            public override void Send(SendOrPostCallback d, object state)
            {
                throw new NotSupportedException("Synchronously sending is not supported.");
            }

            /// <summary>Runs an loop to process all queued work items.</summary>
            public void RunOnCurrentThread()
            {
                foreach (var workItem in m_queue.GetConsumingEnumerable())
                    workItem.Key(workItem.Value);
            }

            /// <summary>Notifies the context that no more work will arrive.</summary>
            public void Complete() { m_queue.CompleteAdding(); }

            /// <summary>Invoked when an async operation is started.</summary>
            public override void OperationStarted()
            {
                if (m_trackOperations)
                    Interlocked.Increment(ref m_operationCount);
            }

            /// <summary>Invoked when an async operation is completed.</summary>
            public override void OperationCompleted()
            {
                if (m_trackOperations &&
                    Interlocked.Decrement(ref m_operationCount) == 0)
                    Complete();
            }
        }
    }
}

Uso:

AsyncPump.Run(() => FooAsync(...));

Una descrizione più dettagliata della pompa asincrona è disponibile qui .



Questo non funziona in uno scenario Asp.net, in quanto è possibile perdere casualmente HttpContext.Current.
Josh Mouch,

12

A chiunque presti più attenzione a questa domanda ...

Se guardi dentro Microsoft.VisualStudio.Services.WebApic'è una classe chiamata TaskExtensions. All'interno di quella classe vedrai il metodo di estensione statica Task.SyncResult(), che come totalmente blocca il thread fino a quando l'attività non ritorna.

Internamente chiama task.GetAwaiter().GetResult()che è piuttosto semplice, tuttavia è sovraccarico di lavorare su qualsiasi asyncmetodo che ritorni Task, Task<T>o Task<HttpResponseMessage>... zucchero sintattico, piccola ... papà ha un debole per i dolci.

Sembra che ...GetAwaiter().GetResult()sia il modo ufficiale MS per eseguire il codice asincrono in un contesto di blocco. Sembra funzionare molto bene per il mio caso d'uso.


3
Mi hai fatto "come blocchi totalmente solo".
Dawood ibn Kareem,

9
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);

OpenIdConnectConfiguration config = result.GetAwaiter().GetResult();

Oppure usa questo:

var result=result.GetAwaiter().GetResult().AccessToken

6

Puoi chiamare qualsiasi metodo asincrono dal codice sincrono, cioè fino a quando non è necessario awaitsu di essi, nel qual caso devono essere contrassegnati asyncanche.

Come molte persone stanno suggerendo qui, potresti chiamare Wait () o Result sull'attività risultante nel tuo metodo sincrono, ma poi finisci con una chiamata di blocco in quel metodo, che in qualche modo sconfigge lo scopo di asincrono.

Se davvero non riesci a creare il tuo metodo asynce non vuoi bloccare il metodo sincrono, allora dovrai usare un metodo di callback passandolo come parametro al metodo ContinueWith sull'attività.


5
Quindi questo non chiamerebbe il metodo in modo sincrono ora lo farebbe?
Jeff Mercado,

2
A quanto ho capito, la domanda era: puoi chiamare un metodo asincrono da un metodo non asincrono. Ciò non implica dover chiamare il metodo asincrono in modo bloccante.
base2

Mi dispiace, il tuo "devono essere contrassegnati asynctroppo" ha attirato la mia attenzione da quello che stavi davvero dicendo.
Jeff Mercado,

Se non mi interessa davvero l'asincrono, va bene chiamarlo in questo modo (e per quanto riguarda la possibilità di deadlock in eccezioni avanzate che Stephen Cleary continua a tormentare?) Ho alcuni metodi di test (che devono essere eseguiti in modo sincrono) che verifica i metodi asincroni. Devo attendere il risultato prima di continuare, quindi posso testare il risultato del metodo asincrono.
timore l'

6

So di essere così in ritardo. Ma nel caso in cui qualcuno come me volesse risolverlo in modo semplice e ordinato e senza dipendere da un'altra biblioteca.

Ho trovato il seguente codice da Ryan

public static class AsyncHelpers
{
    private static readonly TaskFactory taskFactory = new
        TaskFactory(CancellationToken.None,
            TaskCreationOptions.None,
            TaskContinuationOptions.None,
            TaskScheduler.Default);

    /// <summary>
    /// Executes an async Task method which has a void return value synchronously
    /// USAGE: AsyncUtil.RunSync(() => AsyncMethod());
    /// </summary>
    /// <param name="task">Task method to execute</param>
    public static void RunSync(Func<Task> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();

    /// <summary>
    /// Executes an async Task<T> method which has a T return type synchronously
    /// USAGE: T result = AsyncUtil.RunSync(() => AsyncMethod<T>());
    /// </summary>
    /// <typeparam name="TResult">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static TResult RunSync<TResult>(Func<Task<TResult>> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();
}

allora puoi chiamarlo così

var t = AsyncUtil.RunSync<T>(() => AsyncMethod<T>());

6
Sembra esattamente la risposta di cui sopra : mi manca qualcosa
inlokesh,

2

Dopo ore di provare diversi metodi, con più o meno successo, questo è quello con cui sono finito. Non si esaurisce in un deadlock mentre ottiene il risultato e ottiene e genera anche l'eccezione originale e non quella chiusa.

private ReturnType RunSync()
{
  var task = Task.Run(async () => await myMethodAsync(agency));
  if (task.IsFaulted && task.Exception != null)
  {
    throw task.Exception;
  }

  return task.Result;
}

1
Funziona con return task.GetAwaiter (). GetResult ();
Per G

sì, ma per quanto riguarda l'eccezione originale?
Jiří Herník il

Penso che sia sostanzialmente lo stesso di .GetAwaiter (). GetResult ()
Per G

-2

Potrebbe essere chiamato da un nuovo thread (NON dal pool di thread!):

public static class SomeHelperClass
{ 
       public static T Result<T>(Func<T> func)
        {
            return Task.Factory.StartNew<T>(
                  () => func()
                , TaskCreationOptions.LongRunning
                ).Result;
        }
}
...
content = SomeHelperClass.Result<string>(
  () => response.Content.ReadAsStringAsync().Result
  );

-3

Quei metodi asincroni di Windows hanno un piccolo metodo carino chiamato AsTask (). Puoi usarlo per fare in modo che il metodo si restituisca come attività in modo da poter chiamare manualmente Wait () su di esso.

Ad esempio, in un'applicazione Silverlight per Windows Phone 8, è possibile effettuare le seguenti operazioni:

private void DeleteSynchronous(string path)
{
    StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
    Task t = localFolder.DeleteAsync(StorageDeleteOption.PermanentDelete).AsTask();
    t.Wait();
}

private void FunctionThatNeedsToBeSynchronous()
{
    // Do some work here
    // ....

    // Delete something in storage synchronously
    DeleteSynchronous("pathGoesHere");

    // Do other work here 
    // .....
}

Spero che sia di aiuto!


-4

Se vuoi eseguirlo Sincronizza

MethodAsync().RunSynchronously()

3
Questo metodo è destinato all'avvio di attività a freddo. In genere i metodi asincroni restituiscono un'attività attiva, in altre parole un'attività già avviata. la chiamata RunSynchronously()a un'attività calda risulta in un InvalidOperationException. Provalo con questo codice:Task.Run(() => {}).RunSynchronously();
Theodor Zoulias,

-5
   //Example from non UI thread -    
   private void SaveAssetAsDraft()
    {
        SaveAssetDataAsDraft();
    }
    private async Task<bool> SaveAssetDataAsDraft()
    {
       var id = await _assetServiceManager.SavePendingAssetAsDraft();
       return true;   
    }
   //UI Thread - 
   var result = Task.Run(() => SaveAssetDataAsDraft().Result).Result;

2
Genera deadlock. Meglio eliminare la risposta.
PreguntonCojoneroCabrón

Task.Run (() => SaveAssetDataAsDraft ()). Risultato; - non genera deadlock
Anubi
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.