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.

-1voto

ogggre Punkte 2014

Das Ausführen einer asynchronen Aufgabe aus synchronem Code stellt in herkömmlichem .NET einige ziemlich große Herausforderungen dar:

  • Der Wartevorgang für eine asynchrone Aufgabe ist anfällig für Deadlocks, wenn kein geeigneter Synchronisationskontext vorhanden ist
  • Die Annullierungsmodelle von sync und async Code können unterschiedlich und inkompatibel sein

Ich denke wirklich, dass wir diese Interoperabilitätsfunktionalität in .NET BCL haben sollten. In der Zwischenzeit können Sie TaskBridge Klasse von Gapotchenko.FX.Threading NuGet-Paket. Es bietet nahtlose Interoperabilität zwischen synchronen und asynchronen Codeausführungsmodellen:

using Gapotchenko.FX.Threading.Tasks;
using System;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        TaskBridge.Execute(RunAsync);
    }

    static async Task RunAsync()
    {
        await Console.Out.WriteLineAsync("Hello, Async World!");
    }
}

Das NuGet-Paket mit TaskBridge Klasse ist verfügbar aquí .


Annullierungsmodelle

TaskBridge bietet automatische Interoperabilität zwischen verschiedenen Stornierungsmodellen.

Rufen wir eine abbrechbare asynchrone Methode von einem synchronen Thread aus auf, die mit Thread.Abort() Methode:

void SyncMethod() // can be canceled by Thread.Abort()
{
    // Executes an async task that is gracefully canceled via cancellation
    // token when current thread is being aborted or interrupted.
    TaskBridge.Execute(DoJobAsync); // <-- TaskBridge DOES THE MAGIC
}

async Task DoJobAsync(CancellationToken ct)
{
    …
    // Gracefully handles cancellation opportunities.
    ct.ThrowIfCancellationRequested();
    …
}

Schauen wir uns nun das umgekehrte Szenario an, bei dem eine abbrechbare asynchrone Aufgabe einen abbrechbaren synchronen Code aufruft:

async Task DoJobAsync(CancellationToken ct) // can be canceled by a specified cancellation token
{
    // Executes a synchronous method that is thread-aborted when
    // a specified cancellation token is being canceled.
    await TaskBridge.ExecuteAsync(SyncMethod, ct); // <-- TaskBridge DOES THE MAGIC
}

void SyncMethod()
{
    …
}

Der obige Code demonstriert einen einfachen Einzeiler für eine vollständige Interoperabilität zwischen zwei Ausführungsmodellen.

Anmerkung: die neueren Versionen von .NET, beginnend mit .NET Core, unterstützen nicht Thread.Abort() . Das ist nicht weiter schlimm, denn Sie können stattdessen einfach ein Stornierungs-Token an die stornierbaren Methoden übergeben.

-2voto

Jhollman Punkte 1950

Ich weiß, dass dies eine alte Frage ist, aber ich möchte meine Lösung mit Ihnen teilen, die vielleicht nicht die beste ist, aber den Zweck erfüllt:

//Declare an Event Handler:
private event EventHandler OnThemeApply;

//..somewhere in your code..

//we 'hear' the event handler
this.OnThemeApply += (object _Sender, EventArgs _E) =>
{
  //Reaches here After the Async method had finished

  this.OnThemeApply = null;
};
MyAsycMethod();

private void MyAsycMethod()
{
   var t = System.Threading.Tasks.Task.Factory.StartNew(delegate
   {
      //Do something here

      Invoke((MethodInvoker)(() =>
      {
         if(this.OnThemeApply != null) this.OnThemeApply(null, null); //<- Calls the Event
      }));
   });
}

-3voto

user2113284 Punkte 3

Auf wp8:

Einpacken:

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

Nennen Sie es:

GetCustomersSynchronously();

-4voto

Curtis Punkte 5478

Ich habe festgestellt, dass SpinWait dafür recht gut geeignet ist.

var task = Task.Run(()=>DoSomethingAsyncronous());

if(!SpinWait.SpinUntil(()=>task.IsComplete, TimeSpan.FromSeconds(30)))
{//Task didn't complete within 30 seconds, fail...
   return false;
}

return true;

Der obige Ansatz braucht weder .Result noch .Wait() zu verwenden. Außerdem können Sie eine Zeitüberschreitung angeben, damit Sie nicht für immer feststecken, falls die Aufgabe nie abgeschlossen wird.

-5voto

ksemenenko Punkte 1
    private int GetSync()
    {
        try
        {
            ManualResetEvent mre = new ManualResetEvent(false);
            int result = null;

            Parallel.Invoke(async () =>
            {
                result = await SomeCalcAsync(5+5);
                mre.Set();
            });

            mre.WaitOne();
            return result;
        }
        catch (Exception)
        {
            return null;
        }
    }

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