practice - when use async await in c#




Come dovrei eseguire un task asincrono<T> metodo in modo sincrono? (16)

Sto imparando async / await, e mi sono imbattuto in una situazione in cui ho bisogno di chiamare un metodo asincrono in modo sincrono. Come lo posso fare?

Metodo asincrono:

public async Task<Customers> GetCustomers()
{
    return await Service.GetCustomersAsync();
}

Utilizzo normale:

public async void GetCustomers()
{
    customerList = await GetCustomers();
}

Ho provato a utilizzare il seguente:

Task<Customer> task = GetCustomers();
task.Wait()

Task<Customer> task = GetCustomers();
task.RunSynchronously();

Task<Customer> task = GetCustomers();
while(task.Status != TaskStatus.RanToCompletion)

Ho anche provato un suggerimento da here , tuttavia non funziona quando il dispatcher è in uno stato sospeso.

public static void WaitWithPumping(this Task task) 
{
        if (task == null) throw new ArgumentNullException(“task”);
        var nestedFrame = new DispatcherFrame();
        task.ContinueWith(_ => nestedFrame.Continue = false);
        Dispatcher.PushFrame(nestedFrame);
        task.Wait();
}

Ecco l'eccezione e lo stack trace dalla chiamata RunSynchronously :

System.InvalidOperationException

Messaggio : RunSynchronously potrebbe non essere chiamato su un'attività non associata a un delegato.

InnerException : null

Fonte : mscorlib

StackTrace :

          at System.Threading.Tasks.Task.InternalRunSynchronously(TaskScheduler scheduler)
   at System.Threading.Tasks.Task.RunSynchronously()
   at MyApplication.CustomControls.Controls.MyCustomControl.CreateAvailablePanelList() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 638
   at MyApplication.CustomControls.Controls.MyCustomControl.get_AvailablePanels() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 233
   at MyApplication.CustomControls.Controls.MyCustomControl.<CreateOpenPanelList>b__36(DesktopPanel panel) in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 597
   at System.Collections.Generic.List`1.ForEach(Action`1 action)
   at MyApplication.CustomControls.Controls.MyCustomControl.<CreateOpenPanelList>d__3b.MoveNext() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 625
   at System.Runtime.CompilerServices.TaskAwaiter.<>c__DisplayClass7.<TrySetContinuationForAwait>b__1(Object state)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.DispatcherOperation.InvokeImpl()
   at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
   at System.Threading.ExecutionContext.runTryCode(Object userData)
   at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Windows.Threading.DispatcherOperation.Invoke()
   at System.Windows.Threading.Dispatcher.ProcessQueue()
   at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.Dispatcher.InvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
   at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
   at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
   at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.Run()
   at System.Windows.Application.RunDispatcher(Object ignore)
   at System.Windows.Application.RunInternal(Window window)
   at System.Windows.Application.Run(Window window)
   at System.Windows.Application.Run()
   at MyApplication.App.Main() in C:\Documents and Settings\...\MyApplication\obj\Debug\App.g.cs:line 50
   at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
   at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()

Sto imparando async / await, e mi sono imbattuto in una situazione in cui ho bisogno di chiamare un metodo asincrono in modo sincrono. Come lo posso fare?

La risposta migliore è che non lo fai , con i dettagli che dipendono da cosa è la "situazione".

È un getter / setter di proprietà? Nella maggior parte dei casi, è meglio avere metodi asincroni che "proprietà asincrone". (Per maggiori informazioni, vedi il mio post sul blog sulle proprietà asincrone ).

Si tratta di un'app MVVM e si desidera eseguire l'associazione asincrona dei dati? Quindi utilizzare qualcosa come il mio NotifyTask , come descritto nel mio articolo MSDN sull'associazione dati asincrona .

È un costruttore? Quindi probabilmente vorrai considerare un metodo factory asincrono. (Per maggiori informazioni, vedi il mio post sul blog sui costruttori asincroni ).

C'è quasi sempre una risposta migliore rispetto a sync-over-async.

Se non è possibile per la tua situazione (e lo sai ponendo una domanda qui descrivendo la situazione ), allora ti consiglio di usare solo il codice sincrono. Async fino in fondo è la cosa migliore; la sincronizzazione completa è la seconda migliore. Sync-over-async non è raccomandato.

Tuttavia, ci sono una serie di situazioni in cui è necessario sincronizzare-asincrono. Nello specifico, sei vincolato dal codice chiamante in modo che devi essere sincronizzato (e non avere assolutamente modo di ripensare o ricostruire il tuo codice per consentire l'asincronia), e devi chiamare il codice asincrono. Questa è una situazione molto rara, ma di tanto in tanto si presenta.

In tal caso, è necessario utilizzare uno degli hack descritti nel mio articolo sullo sviluppo async brownfield , in particolare:

  • Blocco (ad esempio, GetAwaiter().GetResult() ). Si noti che questo può causare deadlock (come descrivo sul mio blog).
  • Esecuzione del codice su un thread del pool di thread (ad esempio Task.Run(..).GetAwaiter().GetResult() ). Si noti che ciò funzionerà solo se il codice asincrono può essere eseguito su un thread pool di thread (cioè, non dipende da un contesto UI o ASP.NET).
  • Loop di messaggi annidati. Si noti che ciò funzionerà solo se il codice asincrono presuppone solo un contesto a thread singolo, non un tipo di contesto specifico (molte interfacce utente e codice ASP.NET prevedono un contesto specifico).

I loop di messaggi annidati sono i più pericolosi di tutti gli hack, perché causano il re-entrancy . Re-entrancy è estremamente difficile da ragionare, e (IMO) è la causa della maggior parte dei bug di applicazioni su Windows. In particolare, se si è sul thread dell'interfaccia utente e si blocca su una coda di lavoro (in attesa del completamento del lavoro asincrono), il CLR esegue effettivamente alcuni messaggi di pumping per l'utente: gestirà effettivamente alcuni messaggi Win32 dall'interno del proprio codice . Oh, e non hai idea di quali messaggi - quando Chris Brumme dice "Non sarebbe bello sapere esattamente cosa verrà pompato? Sfortunatamente, pompare è un'arte nera che va oltre la comprensione dei mortali". , quindi non abbiamo davvero alcuna speranza di sapere.

Quindi, quando blocchi in questo modo su un thread dell'interfaccia utente, stai cercando dei problemi. Un'altra citazione interessante dello stesso articolo: "Di tanto in tanto, i clienti all'interno o all'esterno dell'azienda scoprono che stiamo pompando messaggi durante il blocco gestito su un STA [thread UI]. Si tratta di una preoccupazione legittima, perché sanno che è molto difficile scrivere un codice robusto di fronte alla rientranza ".

Sì. Codice molto difficile da scrivere che è robusto di fronte alla rientranza. E i loop di messaggi annidati ti costringono a scrivere un codice che sia solido di fronte alla rientranza. Questo è il motivo per cui la risposta accettata (e più votata) per questa domanda è estremamente pericolosa nella pratica.

Se sei completamente fuori da tutte le altre opzioni - non puoi riprogettare il tuo codice, non puoi ristrutturarlo in modo asincrono - sei costretto dal codice chiamante non modificabile per essere sincronizzato - non puoi cambiare il codice downstream per essere sincronizzato - Non puoi bloccare - non puoi eseguire il codice asincrono su un thread separato - allora e solo allora dovresti considerare di abbracciare la rientranza.

Se ti trovi in ​​questo angolo, ti consiglio di utilizzare qualcosa come Dispatcher.PushFrame per le app WPF , il looping con Application.DoEvents per le app WinForm e, nel caso generale, il mio AsyncContext.Run .


È molto più semplice eseguire l'attività nel pool di thread, piuttosto che cercare di ingannare lo scheduler per eseguirlo in modo sincrono. In questo modo puoi essere certo che non si bloccherà. Le prestazioni sono influenzate dall'interruttore di contesto.

Task<MyResult> DoSomethingAsync() { ... }

// Starts the asynchronous task on a thread-pool thread.
// Returns a proxy to the original task.
Task<MyResult> task = Task.Run(() => DoSomethingAsync());

// Will block until the task is completed...
MyResult result = task.Result; 

Funziona bene per me

public static class TaskHelper
{
    public static void RunTaskSynchronously(this Task t)
    {
        var task = Task.Run(async () => await t);
        task.Wait();
    }

    public static T RunTaskSynchronously<T>(this Task<T> t)
    {
        T res = default(T);
        var task = Task.Run(async () => res = await t);
        task.Wait();
        return res;
    }
}

Ho trovato questo codice nel componente Microsoft.AspNet.Identity.Core e funziona.

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

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

L'ho affrontato alcune volte, principalmente nei test di unità o in uno sviluppo di servizi di Windows. Attualmente uso sempre questa funzione:

        var runSync = Task.Factory.StartNew(new Func<Task>(async () =>
        {
            Trace.WriteLine("Task runSync Start");
            await TaskEx.Delay(2000); // Simulates a method that returns a task and
                                      // inside it is possible that there
                                      // async keywords or anothers tasks
            Trace.WriteLine("Task runSync Completed");
        })).Unwrap();
        Trace.WriteLine("Before runSync Wait");
        runSync.Wait();
        Trace.WriteLine("After runSync Waited");

È semplice, facile e non ho avuto problemi.


Nel tuo codice, la tua prima attesa è che l'attività venga eseguita, ma non l'hai avviata in modo che sia in attesa indefinitamente. Prova questo:

Task<Customer> task = GetCustomers();
task.RunSynchronously();

Modificare:

Dici di avere un'eccezione. Per favore pubblica più dettagli, inclusa la traccia dello stack.
Mono contains il seguente test case:

[Test]
public void ExecuteSynchronouslyTest ()
{
        var val = 0;
        Task t = new Task (() => { Thread.Sleep (100); val = 1; });
        t.RunSynchronously ();

        Assert.AreEqual (1, val);
}

Controlla se questo funziona per te. Se non lo fa, anche se molto improbabile, potresti avere qualche strana build di Async CTP. Se funziona, potresti voler esaminare che cosa genera esattamente il compilatore e in che modo l' Taskistanziazione è diversa da questo campione.

Modifica n. 2:

Ho controllato con Reflector che l'eccezione che hai descritto si verifica quando m_actionè null. Questo è un po 'strano, ma non sono esperto di Async CTP. Come ho detto, si dovrebbe decompilare il codice e vedere come esattamente Taskviene istanziato alcun come mai la sua m_actionè null.

PS Qual è l'accordo con i downvotes occasionali? Cura di elaborare?


Sorpreso nessuno ha menzionato questo:

public Task<int> BlahAsync()
{
    // ...
}

int result = BlahAsync().GetAwaiter().GetResult();

Non è bello come alcuni degli altri metodi qui, ma ha i seguenti vantaggi:

  • non ingoiare le eccezioni (come Wait )
  • non tratterà alcuna eccezione generata in una AggregateException (come Result )
  • funziona sia per Task che per Task<T> ( provalo tu stesso! )

Inoltre, dal momento che GetAwaiter è digitato anatra, questo dovrebbe funzionare per qualsiasi oggetto restituito da un metodo asincrono (come ConfiguredAwaitable o YieldAwaitable ), non solo Task.

modifica: si noti che è possibile che questo approccio (o utilizzo di .Result ) si blocchi, a meno che non si .ConfigureAwait(false) aggiungere .ConfigureAwait(false) ogni volta che si attende, per tutti i metodi asincroni che possono essere raggiunti da BlahAsync() (non solo quelli che chiama direttamente). Explanation

// In BlahAsync() body
await FooAsync(); // BAD!
await FooAsync().ConfigureAwait(false); // Good... but make sure FooAsync() and
                                        // all its descendants use ConfigureAwait(false)
                                        // too. Then you can be sure that
                                        // BlahAsync().GetAwaiter().GetResult()
                                        // won't deadlock.

Se sei troppo pigro per aggiungere .ConfigureAwait(false) ovunque, e non ti importa delle prestazioni puoi in alternativa fare

Task.Run(() => BlahAsync()).GetAwaiter().GetResult()

Sappi che questa risposta ha tre anni. L'ho scritto basandomi principalmente su un'esperienza con .Net 4.0, e molto poco con 4.5 specialmente con async-await . In generale è una soluzione semplice, ma a volte rompe le cose. Si prega di leggere la discussione nei commenti.

.Net 4.5

Basta usare questo:

// For Task<T>: will block until the task is completed...
var result = task.Result; 

// For Task (not Task<T>): will block until the task is completed...
task2.RunSynchronously();

Vedi: TaskAwaiter , Task.Result , Task.RunSynchronously

.Net 4.0

Usa questo:

var x = (IAsyncResult)task;
task.Start();

x.AsyncWaitHandle.WaitOne();

...o questo:

task.Start();
task.Wait();

Penso che il seguente metodo di supporto potrebbe anche risolvere il problema.

private TResult InvokeAsyncFuncSynchronously<TResult>(Func< Task<TResult>> func)
    {
        TResult result = default(TResult);
        var autoResetEvent = new AutoResetEvent(false);

        Task.Run(async () =>
        {
            try
            {
                result = await func();
            }
            catch (Exception exc)
            {
                mErrorLogger.LogError(exc.ToString());
            }
            finally
            {
                autoResetEvent.Set();
            }
        });
        autoResetEvent.WaitOne();

        return result;
    }

Può essere utilizzato nel modo seguente:

InvokeAsyncFuncSynchronously(Service.GetCustomersAsync);

Perché non creare una chiamata come:

Service.GetCustomers();

non è asincrono.


Questa risposta è progettata per chiunque utilizzi WPF per .NET 4.5.

Se si tenta di eseguire Task.Run()sul thread della GUI, quindi si task.Wait()bloccherà indefinitamente, se non si dispone della asyncparola chiave nella definizione della funzione.

Questo metodo di estensione risolve il problema controllando per vedere se siamo sul thread GUI e, in tal caso, eseguendo l'attività sul thread dispatcher WPF.

Questa classe può fungere da collante tra il mondo asincrono / atteso e il mondo non asincrono / atteso, in situazioni in cui è inevitabile, come le proprietà MVVM o le dipendenze su altre API che non utilizzano async / await.

/// <summary>
///     Intent: runs an async/await task synchronously. Designed for use with WPF.
///     Normally, under WPF, if task.Wait() is executed on the GUI thread without async
///     in the function signature, it will hang with a threading deadlock, this class 
///     solves that problem.
/// </summary>
public static class TaskHelper
{
    public static void MyRunTaskSynchronously(this Task task)
    {
        if (MyIfWpfDispatcherThread)
        {
            var result = Dispatcher.CurrentDispatcher.InvokeAsync(async () => { await task; });
            result.Wait();
            if (result.Status != DispatcherOperationStatus.Completed)
            {
                throw new Exception("Error E99213. Task did not run to completion.");
            }
        }
        else
        {
            task.Wait();
            if (task.Status != TaskStatus.RanToCompletion)
            {
                throw new Exception("Error E33213. Task did not run to completion.");
            }
        }
    }

    public static T MyRunTaskSynchronously<T>(this Task<T> task)
    {       
        if (MyIfWpfDispatcherThread)
        {
            T res = default(T);
            var result = Dispatcher.CurrentDispatcher.InvokeAsync(async () => { res = await task; });
            result.Wait();
            if (result.Status != DispatcherOperationStatus.Completed)
            {
                throw new Exception("Error E89213. Task did not run to completion.");
            }
            return res;
        }
        else
        {
            T res = default(T);
            var result = Task.Run(async () => res = await task);
            result.Wait();
            if (result.Status != TaskStatus.RanToCompletion)
            {
                throw new Exception("Error E12823. Task did not run to completion.");
            }
            return res;
        }
    }

    /// <summary>
    ///     If the task is running on the WPF dispatcher thread.
    /// </summary>
    public static bool MyIfWpfDispatcherThread
    {
        get
        {
            return Application.Current.Dispatcher.CheckAccess();
        }
    }
}

Questo è un lavoro per me

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

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

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

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

    class SomeClass
    {
        public async Task<object> LoginAsync(object loginInfo)
        {
            return await Task.FromResult(0);
        }
        public object Login(object loginInfo)
        {
            return AsyncHelper.RunSync(() => LoginAsync(loginInfo));
            //return this.LoginAsync(loginInfo).Result.Content;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var someClass = new SomeClass();

            Console.WriteLine(someClass.Login(1));
            Console.ReadLine();
        }
    }
}

Su wp8:

Avvolgilo:

Task GetCustomersSynchronously()
{
    Task t = new Task(async () =>
    {
        myCustomers = await GetCustomers();
    }
    t.RunSynchronously();
}

Chiamalo:

GetCustomersSynchronously();

usa sotto il codice snip

Task.WaitAll(Task.Run(async () => await service.myAsyncMethod()));


Testato in .Net 4.6. Può anche evitare lo stallo.

Per il ritorno del metodo aysnc Task.

Task DoSomeWork()

Task.Run(async () => await DoSomeWork()).Wait();

Per il ritorno del metodo asincrono Task<T>

Task<T> GetSomeValue()

var result = Task.Run(() => GetSomeValue()).Result;




async-await