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.

26voto

Theo Yaung Punkte 3954

Wenn ich Ihre Frage richtig lese, wird der Code, der den synchronen Aufruf einer asynchronen Methode wünscht, auf einem angehaltenen Dispatcher-Thread ausgeführt. Und Sie wollen tatsächlich synchron Block dieser Thread, bis die asynchrone Methode abgeschlossen ist.

Async-Methoden in C# 5 werden dadurch angetrieben, dass die Methode unter der Haube effektiv in Teile zerlegt wird und ein Task die die Fertigstellung des Ganzen verfolgen kann. Wie die zerhackten Methoden ausgeführt werden, kann jedoch von der Art des Ausdrucks abhängen, der an die Funktion await Betreiber.

Die meiste Zeit werden Sie Folgendes verwenden await auf einen Ausdruck des Typs Task . Task's Umsetzung der await Muster ist insofern "intelligent", als es sich an die SynchronizationContext was im Wesentlichen Folgendes bewirkt:

  1. Wenn der Faden, der in die await auf einem Dispatcher- oder WinForms-Nachrichtenschleifen-Thread ist, stellt er sicher, dass die Chunks der asynchronen Methode als Teil der Verarbeitung der Nachrichtenwarteschlange auftreten.
  2. Wenn der Faden, der in die await auf einem Thread-Pool-Thread ist, dann treten die restlichen Teile der asynchronen Methode überall im Thread-Pool auf.

Das ist der Grund, warum Sie wahrscheinlich Probleme haben - die Implementierung der asynchronen Methode versucht, den Rest auf dem Dispatcher auszuführen, obwohl dieser angehalten ist.

.... Rückwärtsfahren! ....

Ich muss die Frage stellen, por qué versuchen Sie, synchron auf eine asynchrone Methode zu blockieren? Dies würde den Zweck, warum die Methode asynchron aufgerufen werden sollte, zunichte machen. Im Allgemeinen, wenn Sie beginnen mit await auf einen Dispatcher oder eine UI-Methode anwenden, müssen Sie Ihren gesamten UI-Ablauf asynchron machen. Wenn Ihr Callstack zum Beispiel so aussieht wie der folgende:

  1. [Oben] WebRequest.GetResponse()
  2. YourCode.HelperMethod()
  3. YourCode.AnotherMethod()
  4. YourCode.EventHandlerMethod()
  5. [UI Code].Plumbing() - WPF o WinForms Code
  6. [Nachrichtenschleife] - WPF o WinForms Nachrichtenschleife

Sobald der Code für die Verwendung von async umgewandelt wurde, erhalten Sie in der Regel Folgendes

  1. [Oben] WebRequest.GetResponseAsync()
  2. YourCode.HelperMethodAsync()
  3. YourCode.AnotherMethodAsync()
  4. YourCode.EventHandlerMethodAsync()
  5. [UI Code].Plumbing() - WPF o WinForms Code
  6. [Nachrichtenschleife] - WPF o WinForms Nachricht Schleife

Antwortet tatsächlich

Die obige AsyncHelpers-Klasse funktioniert tatsächlich, weil sie sich wie eine verschachtelte Nachrichtenschleife verhält, aber sie installiert ihren eigenen parallelen Mechanismus für den Dispatcher, anstatt zu versuchen, den Dispatcher selbst auszuführen. Das ist ein Workaround für Ihr Problem.

Eine weitere Abhilfe besteht darin, die asynchrone Methode auf einem Threadpool-Thread auszuführen und dann zu warten, bis sie abgeschlossen ist. Dies ist einfach - Sie können es mit dem folgenden Schnipsel tun:

var customerList = TaskEx.RunEx(GetCustomers).Result;

Die endgültige API wird Task.Run(...) sein, aber mit der CTP benötigen Sie die Ex-Suffixe ( Erklärung hier ).

25voto

Clement Punkte 3370

Das funktioniert gut für mich

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

23voto

Liang Punkte 797

Getestet in .Net 4.6. Es kann auch Deadlocks vermeiden.

Bei asynchronen Methoden, die Task .

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

Bei asynchronen Methoden, die Task<T>

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

Editar :

Wenn der Aufrufer im Thread-Pool-Thread läuft (oder der Aufrufer sich auch in einem Task befindet), kann es in manchen Situationen immer noch zu einem Deadlock kommen.

17voto

J. Lennon Punkte 3191

Ich bin schon ein paar Mal damit konfrontiert worden, meistens bei Unit-Tests oder bei der Entwicklung eines Windows-Dienstes. Derzeit verwende ich diese Funktion immer:

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

Es ist einfach, leicht und ich hatte keine Probleme.

17voto

wenhx Punkte 235

Ich habe diesen Code in der Komponente Microsoft.AspNet.Identity.Core gefunden, und er funktioniert.

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

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