760 Stimmen

Wie kann ich eine asynchrone Task<T>-Methode synchron ausführen?

Ich lerne über async/await, und lief in einer Situation, wo ich eine async-Methode synchron aufrufen müssen. Wie kann ich das tun?

Asynchrones Verfahren:

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

Normaler Gebrauch:

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

Ich habe die folgenden Methoden ausprobiert:

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

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

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

Ich habe auch eine Anregung von aquí Es funktioniert jedoch nicht, wenn sich der Dispatcher in einem angehaltenen Zustand befindet.

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

Hier ist die Ausnahme und der Stacktrace vom Aufruf RunSynchronously :

System.InvalidOperationException

Nachricht : RunSynchronously darf nicht für eine Aufgabe aufgerufen werden, die nicht an einen Delegaten gebunden ist.

InnerException : null

Quelle : 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()

54 Stimmen

Die beste Antwort auf die Frage "Wie kann ich eine asynchrone Methode synchron aufrufen" ist "gar nicht". Es gibt Hacks um zu versuchen, sie zum Funktionieren zu zwingen, aber sie haben alle sehr subtile Tücken. Gehen Sie stattdessen zurück und korrigieren Sie den Code, der dies "erfordert".

92 Stimmen

@Stephen Cleary Absolut zustimmen, aber manchmal seine einfach unvermeidlich, z. B. wenn Ihr Code auf einige 3rd-Party-API abhängig ist, die nicht async/await verwendet. Darüber hinaus, wenn die Bindung an WPF-Eigenschaften bei Verwendung von MVVM, seine buchstäblich unmöglich, async/await zu verwenden, da dies nicht auf Eigenschaften unterstützt wird.

5 Stimmen

@StephenCleary Nicht immer. Ich baue eine DLL, die dann in GeneXus . Es unterstützt keine async/await Schlüsselwörter, so dass ich nur synchrone Methoden verwenden muss.

538voto

Rachel Punkte 126340

Hier ist ein Workaround, den ich gefunden habe und der in allen Fällen funktioniert (einschließlich suspendierter Dispatcher). Es ist nicht mein Code und ich bin noch arbeiten, um es vollständig zu verstehen, aber es funktioniert.

Sie kann mit aufgerufen werden:

customerList = AsyncHelpers.RunSync<List<Customer>>(() => GetCustomers());

Der Code stammt von aquí

public static class AsyncHelpers
{
    /// <summary>
    /// Execute's an async Task<T> method which has a void return value synchronously
    /// </summary>
    /// <param name="task">Task<T> method to execute</param>
    public static void RunSync(Func<Task> task)
    {
        var oldContext = SynchronizationContext.Current;
        var synch = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(synch);
        synch.Post(async _ =>
        {
            try
            {
                await task();
            }
            catch (Exception e)
            {
                synch.InnerException = e;
                throw;
            }
            finally
            {
                synch.EndMessageLoop();
            }
        }, null);
        synch.BeginMessageLoop();

        SynchronizationContext.SetSynchronizationContext(oldContext);
    }

    /// <summary>
    /// Execute's an async Task<T> method which has a T return type synchronously
    /// </summary>
    /// <typeparam name="T">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static T RunSync<T>(Func<Task<T>> task)
    {
        var oldContext = SynchronizationContext.Current;
        var synch = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(synch);
        T ret = default(T);
        synch.Post(async _ =>
        {
            try
            {
                ret = await task();
            }
            catch (Exception e)
            {
                synch.InnerException = e;
                throw;
            }
            finally
            {
                synch.EndMessageLoop();
            }
        }, null);
        synch.BeginMessageLoop();
        SynchronizationContext.SetSynchronizationContext(oldContext);
        return ret;
    }

    private class ExclusiveSynchronizationContext : SynchronizationContext
    {
        private bool done;
        public Exception InnerException { get; set; }
        readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false);
        readonly Queue<Tuple<SendOrPostCallback, object>> items =
            new Queue<Tuple<SendOrPostCallback, object>>();

        public override void Send(SendOrPostCallback d, object state)
        {
            throw new NotSupportedException("We cannot send to our same thread");
        }

        public override void Post(SendOrPostCallback d, object state)
        {
            lock (items)
            {
                items.Enqueue(Tuple.Create(d, state));
            }
            workItemsWaiting.Set();
        }

        public void EndMessageLoop()
        {
            Post(_ => done = true, null);
        }

        public void BeginMessageLoop()
        {
            while (!done)
            {
                Tuple<SendOrPostCallback, object> task = null;
                lock (items)
                {
                    if (items.Count > 0)
                    {
                        task = items.Dequeue();
                    }
                }
                if (task != null)
                {
                    task.Item1(task.Item2);
                    if (InnerException != null) // the method threw an exeption
                    {
                        throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException);
                    }
                }
                else
                {
                    workItemsWaiting.WaitOne();
                }
            }
        }

        public override SynchronizationContext CreateCopy()
        {
            return this;
        }
    }
}

367voto

AK_ Punkte 7693

Beachten Sie diese Antwort ist drei Jahre alt. Ich habe sie hauptsächlich auf der Grundlage meiner Erfahrungen mit .Net 4.0 geschrieben, und nur sehr wenig mit 4.5, insbesondere mit async-await . Im Großen und Ganzen ist das eine schöne, einfache Lösung, aber sie macht manchmal etwas kaputt. Bitte lesen Sie die Diskussion in den Kommentaren.

.Net 4.5

Verwenden Sie einfach dies:

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

Siehe: TaskAwaiter , Aufgabe.Ergebnis , Task.RunSynchronously


.Net 4.0

Verwenden Sie dies:

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

x.AsyncWaitHandle.WaitOne();

...oder dies:

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

195voto

James Ko Punkte 28503

Ich bin überrascht, dass dies noch niemand erwähnt hat:

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

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

Sie ist nicht so schön wie einige der anderen Methoden hier, hat aber folgende Vorteile:

  • es verschluckt keine Ausnahmen (wie Wait )
  • werden keine Ausnahmen verpackt, die in einer AggregateException (wie Result )
  • funktioniert für beide Task y Task<T> ( Probieren Sie es selbst aus! )

Außerdem, da GetAwaiter duck-typed ist, sollte dies für jedes Objekt funktionieren, das von einer asynchronen Methode zurückgegeben wird (wie ConfiguredAwaitable o YieldAwaitable ), nicht nur Tasks.


bearbeiten: Bitte beachten Sie, dass es möglich ist, mit diesem Ansatz (oder mit .Result ) in die Sackgasse, es sei denn, Sie fügen .ConfigureAwait(false) jedes Mal, wenn Sie warten, für alle asynchronen Methoden, die möglicherweise von BlahAsync() (nicht nur solche, die er direkt aufruft). Erläuterung .

// 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.

Wenn Sie zu faul sind, etwas hinzuzufügen .ConfigureAwait(false) überall, und Sie kümmern sich nicht um die Leistung können Sie alternativ tun

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

81voto

Michael L Perry Punkte 7177

Es ist viel einfacher, die Aufgabe im Thread-Pool auszuführen, als zu versuchen, den Scheduler auszutricksen, damit er sie synchron ausführt. Auf diese Weise können Sie sicher sein, dass es nicht zu einem Deadlock kommt. Die Leistung wird durch den Kontextwechsel beeinträchtigt.

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;

73voto

Stephen Cleary Punkte 402664

Ich lerne über async/await, und lief in einer Situation, wo ich eine async-Methode synchron aufrufen müssen. Wie kann ich das tun?

Die beste Antwort ist du nicht wobei die Einzelheiten von der jeweiligen "Situation" abhängen.

Handelt es sich um eine Eigenschaft getter/setter? In den meisten Fällen ist es besser, asynchrone Methoden zu haben als "asynchrone Eigenschaften". (Für weitere Informationen, siehe mein Blogbeitrag zu asynchronen Eigenschaften ).

Ist dies eine MVVM-Anwendung und Sie wollen asynchrone Datenbindung zu tun? Dann verwenden Sie etwas wie meine NotifyTask wie in meinem Bericht MSDN-Artikel über asynchrone Datenbindung .

Ist es ein Konstruktor? Dann sollten Sie wahrscheinlich eine asynchrone Fabrikmethode in Betracht ziehen. (Weitere Informationen finden Sie in meinem Blogbeitrag über asynchrone Konstruktoren ).

Es gibt fast immer eine bessere Lösung als eine Synchronisierung über eine Asynchronisierung.

Wenn dies in Ihrer Situation nicht möglich ist (und Sie dies wissen, indem Sie hier eine Frage stellen Beschreibung der Situation ), dann würde ich empfehlen, nur synchronen Code zu verwenden. Async den ganzen Weg ist am besten; sync den ganzen Weg ist zweitbeste. Sync-over-async wird nicht empfohlen.

Es gibt jedoch eine Handvoll Situationen, in denen Sync-over-Async notwendig ist. Insbesondere sind Sie durch den aufrufenden Code gezwungen, dass Sie haben zu synchronisieren (und haben absolut keine Möglichkeit, Ihren Code zu überdenken oder umzustrukturieren, um Asynchronität zu ermöglichen), y Sie haben um asynchronen Code aufzurufen. Dies ist eine sehr eine seltene Situation, aber sie kommt von Zeit zu Zeit vor.

In diesem Fall müssen Sie einen der Hacks verwenden, die in meinem Artikel über Industriebrache async Entwicklung konkret:

  • Blockieren (z.B., GetAwaiter().GetResult() ). Beachten Sie, dass dies kann zu Deadlocks führen (wie ich in meinem Blog beschreibe).
  • Die Ausführung des Codes auf einem Thread-Pool-Thread (z. B., Task.Run(..).GetAwaiter().GetResult() ). Beachten Sie, dass dies nur funktioniert, wenn der asynchrone Code auf einem Threadpool-Thread ausgeführt werden kann (d. h. nicht von einem UI- oder ASP.NET-Kontext abhängig ist).
  • Verschachtelte Nachrichtenschleifen. Beachten Sie, dass dies nur funktioniert, wenn der asynchrone Code nur von einem Single-Thread-Kontext ausgeht, nicht von einem spezifisch Kontexttyp (viele UI- und ASP.NET-Codes erwarten einen bestimmten Kontext).

Verschachtelte Nachrichtenschleifen sind der gefährlichste aller Hacks, denn sie verursachen Wiedereintritt in den Ruhestand . Reentrancy ist extrem schwierig zu erklären und ist (IMO) die Ursache für die meisten Anwendungsfehler unter Windows. Insbesondere, wenn Sie auf der UI-Thread sind und Sie blockieren auf einer Arbeits-Warteschlange (warten auf die asynchrone Arbeit zu beenden), dann die CLR tatsächlich tut einige Nachricht Pumpen für Sie - es wird tatsächlich einige Win32 Nachrichten behandeln in Ihrem Code . Oh, und Sie haben keine Ahnung, welche Nachrichten - wann Chris Brumme sagt: "Wäre es nicht toll, genau zu wissen, was gepumpt wird? Leider ist das Pumpen eine schwarze Kunst, die sich dem menschlichen Verstand entzieht." dann haben wir wirklich keine Chance, das zu erfahren.

Wenn Sie also in einem UI-Thread auf diese Weise blockieren, ist Ärger vorprogrammiert. Ein weiteres Zitat von cbrumme aus demselben Artikel: "Von Zeit zu Zeit stellen Kunden innerhalb oder außerhalb des Unternehmens fest, dass wir Nachrichten während des verwalteten Blockierens in einem STA [UI-Thread] pumpen. Das ist eine berechtigte Sorge, denn sie wissen, dass es sehr schwer ist, Code zu schreiben, der angesichts der Wiederholungstendenz robust ist."

Ja, das ist sie. Sehr Es ist schwer, Code zu schreiben, der angesichts der Wiederholungstendenz robust ist. Und verschachtelte Nachrichtenschleifen Kraft Sie können Code schreiben, der angesichts von Wiederholungstendenzen robust ist. Aus diesem Grund die akzeptierte (und meistgewählte) Antwort auf diese Frage es äußerst gefährlich in der Praxis.

Wenn Sie keine anderen Optionen mehr haben - Sie können Ihren Code nicht umgestalten, Sie können ihn nicht umstrukturieren, um asynchron zu sein - Sie sind durch unveränderlichen aufrufenden Code gezwungen, synchron zu sein - Sie können den nachgeschalteten Code nicht ändern, um synchron zu sein - Sie können nicht blockieren - Sie können den asynchronen Code nicht in einem separaten Thread ausführen - dann und erst dann sollten Sie in Erwägung ziehen, die Wiedereingliederung zu übernehmen.

Wenn Sie sich in dieser Situation befinden, würde ich empfehlen, etwas wie Dispatcher.PushFrame für WPF-Anwendungen , Schleifenbildung mit Application.DoEvents für WinForm-Anwendungen, und für den allgemeinen Fall, meine eigene AsyncContext.Run .

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X