wake-up-neo.com

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

Ich lerne von async/await und bin in eine Situation geraten, in der ich eine async-Methode synchron aufrufen muss. Wie kann ich das machen?

Async-Methode:

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

Normaler Gebrauch:

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

Ich habe folgendes versucht: 

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

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

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

Ich habe auch einen Vorschlag aus hier ausprobiert, der jedoch nicht funktioniert, wenn sich der Dispatcher im suspendierten 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 Stack-Trace vom Aufruf von RunSynchronously:

System.InvalidOperationException

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

InnerException: null

Source: 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()
550
Rachel

Hier ist eine Problemumgehung, die für alle Fälle funktioniert (einschließlich suspendierter Dispatcher). Es ist nicht mein Code und ich arbeite immer noch daran, ihn vollständig zu verstehen, aber es funktioniert.

Es kann aufgerufen werden mit:

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

Code ist von hier

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

Seien Sie beraten Diese Antwort ist drei Jahre alt. Ich habe es hauptsächlich basierend auf einer Erfahrung mit .Net 4.0 geschrieben, und sehr wenig mit 4.5, besonders mit async-await. Im Allgemeinen handelt es sich um eine einfache Lösung von Nizza. Bitte lesen Sie die Diskussion in den Kommentaren.

.Net 4.5

Verwenden Sie einfach dieses:

// 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 , Task.Result , Task.RunSynchronously


.Net 4.0

Benutze das:

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

x.AsyncWaitHandle.WaitOne();

...oder dieses:

task.Start();
task.Wait();
302
AK_

Überrascht erwähnte niemand dies:

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

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

Nicht so hübsch wie einige der anderen Methoden hier, aber es hat die folgenden Vorteile:

  • es schluckt keine Ausnahmen (wie Wait)
  • es werden keine Ausnahmen in eine AggregateException geworfen (wie Result).
  • funktioniert sowohl für Task als auch für Task<T> ( probieren Sie es selbst aus! )

Da GetAwaiter vom Typ duck ist, sollte dies für jedes Objekt funktionieren, das von einer asynchronen Methode (wie ConfiguredAwaitable oder YieldAwaitable) zurückgegeben wird, nicht nur für Aufgaben.


edit: Bitte beachten Sie, dass dieser Ansatz (oder die Verwendung von .Result) zu einem Deadlock führen kann, es sei denn, Sie müssen unbedingt jedes Mal, wenn Sie warten, .ConfigureAwait(false) hinzufügen, für alle asynchronen Methoden, die möglicherweise von BlahAsync() aus erreichbar sind (nicht nur eine davon) ruft direkt an). Erklärung .

// 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, .ConfigureAwait(false) überall hinzuzufügen, und Sie sich nicht für die Leistung interessieren, können Sie dies alternativ tun

Task.Run(() => BlahAsync()).GetAwaiter().GetResult()
99
James Ko

Es ist viel einfacher, die Task im Thread-Pool auszuführen, als zu versuchen, den Scheduler dazu zu überreden, ihn synchron auszuführen. Auf diese Weise können Sie sicher sein, dass es nicht blockiert. 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; 
70
Michael L Perry

Ich lerne etwas über async/await und stoße auf eine Situation, in der ich eine asynchrone Methode synchron aufrufen muss. Wie kann ich das machen?

Die beste Antwort ist , die Sie nicht , wobei die Details von der "Situation" abhängen.

Ist es ein Property Getter/Setter? In den meisten Fällen sind asynchrone Methoden besser als "asynchrone Eigenschaften". (Weitere Informationen finden Sie unter mein Blog-Beitrag zu asynchronen Eigenschaften ).

Ist dies eine MVVM-App und möchten Sie eine asynchrone Datenbindung durchführen? Verwenden Sie dann so etwas wie mein NotifyTask , wie in meinem MSDN-Artikel zur asynchronen Datenbindung beschrieben.

Ist es ein Konstruktor? Dann möchten Sie wahrscheinlich eine asynchrone Factory-Methode in Betracht ziehen. (Weitere Informationen finden Sie in meinem Blog-Beitrag zu asynchronen Konstruktoren ).

Es gibt fast immer eine bessere Antwort als "Synchronisieren über Asynchronisieren".

Wenn es für Ihre Situation nicht möglich ist (und Sie wissen dies, indem Sie hier eine Frage stellen Beschreibung der Situation), dann würde ich nur die Verwendung von synchronem Code empfehlen. Asynchrone Verbindungen sind am besten. Die vollständige Synchronisierung ist die zweitbeste. Sync-over-Async wird nicht empfohlen.

Es gibt jedoch einige Situationen, in denen eine Synchronisierung über Asynchronisierung erforderlich ist. Insbesondere sind Sie durch den aufrufenden Code eingeschränkt, sodass Sie have synchron sein müssen (und absolut keine Möglichkeit haben, Ihren Code zu überdenken oder neu zu strukturieren, um Asynchronität zuzulassen), und Sie haben, um den asynchronen Code aufzurufen. Dies ist eine sehr seltene Situation, die jedoch von Zeit zu Zeit auftritt.

In diesem Fall müssten Sie einen der in meinem Artikel zu Brownfield async development beschriebenen Hacks verwenden, insbesondere:

  • Blockieren (z. B. GetAwaiter().GetResult()). Beachten Sie, dass dies kann zu Deadlocks führen (wie ich in meinem Blog beschreibe).
  • Ausführen des Codes in einem Threadpool-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).
  • Geschachtelte Nachrichtenschleifen. Beachten Sie, dass dies nur funktioniert, wenn der asynchrone Code nur einen Kontext mit einem Thread und keinen specific -Kontext-Typ annimmt (viele Benutzeroberflächen- und ASP.NET-Codes erwarten einen bestimmten Kontext).

Geschachtelte Nachrichtenschleifen sind die gefährlichsten aller Hacks, da sie Wiedereintritt verursachen. Das erneute Eintreten ist äußerst schwierig, und (IMO) ist die Ursache für die meisten Anwendungsfehler unter Windows. Insbesondere, wenn Sie sich im UI-Thread befinden und eine Arbeitswarteschlange blockieren (auf den Abschluss der asynchronen Arbeit warten), übernimmt die CLR tatsächlich das Pumpen von Nachrichten für Sie - sie verarbeitet tatsächlich einige Win32-Nachrichten aus deinem Code. Oh, und Sie haben keine Ahnung, welche Nachrichten - wann Chris Brummesagt: "Wäre es nicht toll zu wissen, was genau gepumpt wird? Leider ist Pumpen eine schwarze Kunst, die ist jenseits des sterblichen Verständnisses. " , dann haben wir wirklich keine Hoffnung zu wissen.

Wenn Sie also in einem UI-Thread so blockieren, fragen Sie nach Problemen. Ein weiteres Zitat aus demselben Artikel: "Von Zeit zu Zeit stellen Kunden innerhalb oder außerhalb des Unternehmens fest, dass wir während des verwalteten Blockierens auf einem STA [UI-Thread] Nachrichten senden. Dies ist ein berechtigtes Anliegen, da sie wissen, dass es sehr schwierig ist Code zu schreiben, der robust gegen Wiedereintritt ist. "

Ja ist es. Sehr schwer zu schreibender Code, der robust gegen Wiedereintritt ist. Und verschachtelte Nachrichtenschleifen Force Sie schreiben Code, der robust gegen Wiedereintritt ist. Dies ist der Grund, warum die akzeptierte (und am häufigsten gewählte) Antwort auf diese Frage in der Praxis extrem gefährlich ist.

Wenn Sie keine anderen Optionen haben - Sie können Ihren Code nicht neu entwerfen, Sie können ihn nicht asynchron umstrukturieren - Sie müssen den unveränderlichen aufrufenden Code synchronisieren - Sie können den nachgelagerten Code nicht synchronisieren - Sie können nicht blockieren - Sie können den Async-Code nicht auf einem separaten Thread ausführen - dann und nur dann sollten Sie in Betracht ziehen, erneut einzutreten.

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

50
Stephen Cleary

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

Wenn ich Ihre Frage richtig lese, wird der Code, der den synchronen Aufruf einer asynchronen Methode wünscht, in einem angehaltenen Dispatcher-Thread ausgeführt. Und Sie möchten diesen Thread tatsächlich synchron blockieren , bis die asynchrone Methode abgeschlossen ist.

Asynchrone Methoden in C # 5 werden aktiviert, indem die Methode unter der Motorhaube effektiv in Teile zerlegt und ein Task zurückgegeben wird, das die Gesamtvollendung des gesamten Shabang nachverfolgt. Die Ausführung der zerlegten Methoden kann jedoch vom Typ des Ausdrucks abhängen, der an den Operator await übergeben wird.

Meistens verwenden Sie await für einen Ausdruck vom Typ Task. Die Implementierung des Musters await durch Task ist insofern "intelligent", als es sich auf das Muster SynchronizationContext verschiebt, was im Grunde Folgendes bewirkt:

  1. Wenn sich der Thread, der in await eingibt, in einem Dispatcher- oder WinForms-Nachrichtenschleifenthread befindet, wird sichergestellt, dass die Blöcke der asynchronen Methode als Teil der Verarbeitung der Nachrichtenwarteschlange auftreten.
  2. Wenn sich der Thread, der in await eingibt, in einem Threadpool-Thread befindet, werden die verbleibenden Blöcke der asynchronen Methode an einer beliebigen Stelle im Threadpool angezeigt.

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

.... sichern! ....

Ich muss die Frage stellen, warum versuchen Sie, eine asynchrone Methode synchron zu blockieren? Dies würde den Grund dafür, warum die Methode asynchron aufgerufen werden soll, zunichte machen. Wenn Sie await für einen Dispatcher oder eine UI-Methode verwenden, möchten Sie im Allgemeinen Ihren gesamten UI-Fluss asynchronisieren. Wenn Ihr Callstack beispielsweise wie folgt aussah:

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

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

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

Tatsächlich antworten

Die obige AsyncHelpers-Klasse funktioniert tatsächlich, weil sie sich wie eine verschachtelte Nachrichtenschleife verhält, aber eine eigene parallele Mechanik für den Dispatcher installiert, anstatt zu versuchen, sie auf dem Dispatcher selbst auszuführen. Das ist eine Problemumgehung für Ihr Problem.

Eine andere Problemumgehung besteht darin, die asynchrone Methode in einem Threadpool-Thread auszuführen und dann auf den Abschluss zu warten. Dies ist einfach - Sie können es mit dem folgenden Snippet tun:

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

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

22
Theo Yaung

Die einfachste Möglichkeit, Task auszuführen, ohne den UI-Thread zu blockieren, ist die Verwendung von RunSynchronously () wie folgt:

Task t = new Task(() => 
{ 
   //.... YOUR CODE ....
});
t.RunSynchronously();

In meinem Fall habe ich ein Ereignis, das ausgelöst wird, wenn etwas passiert. Ich weiß nicht, wie oft es vorkommt. Ich verwende den obigen Code in meinem Ereignis. Wenn also ein Ereignis ausgelöst wird, wird eine Aufgabe erstellt. Aufgaben werden synchron ausgeführt und es funktioniert für mich gut. Ich war nur überrascht, dass ich so lange gebraucht habe, um herauszufinden, wie einfach es ist. In der Regel sind Empfehlungen viel komplexer und fehleranfälliger. Dies war einfach und sauber.

18
pixel

Ich habe es einige Male erlebt, meistens in Unit-Tests oder in einer Windows-Dienstentwicklung. Derzeit verwende ich immer diese Funktion:

        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, einfach und ich hatte keine Probleme.

16
J. Lennon

Ich habe diesen Code bei der Komponente Microsoft.AspNet.Identity.Core gefunden und 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();
}
13
wenhx

Nur eine kleine Anmerkung - dieser Ansatz:

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

funktioniert für WinRT.

Lassen Sie mich erklären:

private void TestMethod()
{
    Task<Customer> task = GetCustomers(); // call async method as sync and get task as result
    task.Wait(); // wait executing the method
    var customer = task.Result; // get's result.
    Debug.WriteLine(customer.Name); //print customer name
}
public class Customer
{
    public Customer()
    {
        new ManualResetEvent(false).WaitOne(TimeSpan.FromSeconds(5));//wait 5 second (long term operation)
    }
    public string Name { get; set; }
}
private Task<Customer> GetCustomers()
{
    return Task.Run(() => new Customer
    {
        Name = "MyName"
    });
}

Darüber hinaus funktioniert dieser Ansatz nur für Windows Store-Lösungen!

Hinweis: Dieser Weg ist nicht threadsicher, wenn Sie Ihre Methode innerhalb einer anderen asynchronen Methode aufrufen (entsprechend den Kommentaren von @Servy).

10
RredCat

In Ihrem Code wird wait zuerst ausgeführt, bis der Task ausgeführt wird, Sie haben ihn jedoch noch nicht gestartet. Er wartet also auf unbestimmte Zeit. Versuche dies:

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

Bearbeiten:

Sie sagen, dass Sie eine Ausnahme bekommen. Bitte posten Sie weitere Details, einschließlich Stack-Trace.
Mono enthält den folgenden Testfall:

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

        Assert.AreEqual (1, val);
}

Prüfen Sie, ob dies für Sie funktioniert. Wenn dies nicht der Fall ist, ist es sehr unwahrscheinlich, dass Sie Async-CTP-Builds verwenden. Wenn dies funktioniert, möchten Sie vielleicht untersuchen, was genau der Compiler generiert und wie sich die Task-Instanziierung von diesem Beispiel unterscheidet.

Edit # 2:

Ich habe mit Reflector überprüft, dass die von Ihnen beschriebene Ausnahme auftritt, wenn m_actionnull ist. Das ist irgendwie seltsam, aber ich bin kein Experte für Async CTP. Wie gesagt, sollten Sie Ihren Code dekompilieren und sehen, wie genau Task instanziiert wird, wie auch sein m_actionnull ist.


P.S. Was ist der Deal mit den gelegentlichen Downvotes? Interessieren Sie sich für die Ausarbeitung?

9
Dan Abramov

Warum erstellen Sie nicht einen Anruf wie:

Service.GetCustomers();

das ist nicht asynchron.

7
Daniel A. White

verwenden Sie den folgenden Code-Ausschnitt

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

Diese Antwort ist für alle gedacht, die WPF für .NET 4.5 verwenden.

Wenn Sie versuchen, Task.Run() im GUI-Thread auszuführen, wird task.Wait() auf unbestimmte Zeit hängen bleiben, wenn Sie nicht das Schlüsselwort async in Ihrer Funktionsdefinition haben.

Diese Erweiterungsmethode löst das Problem, indem geprüft wird, ob wir uns im GUI-Thread befinden und wenn dies der Fall ist, die Task im WPF-Dispatcher-Thread ausgeführt wird.

Diese Klasse kann als Bindeglied zwischen der asynchronen/await-Welt und der nicht-asynchronen/await-Welt fungieren, wenn dies unvermeidlich ist, beispielsweise MVVM-Eigenschaften oder Abhängigkeiten von anderen APIs, die nicht async/await verwenden.

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

Wenn Sie einfach .Result; oder .Wait() aufrufen, besteht ein Risiko für Deadlocks. Da die meisten von uns Oneliner mögen, können Sie diese für .Net 4.5< verwenden.

Erfassen eines Wertes über eine asynchrone Methode:

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

Synchrones Aufrufen einer asynchronen Methode

Task.Run(() => asyncMethod()).Wait();

Aufgrund der Verwendung von Task.Run treten keine Deadlock-Probleme auf.

Quelle:

https://stackoverflow.com/a/32429753/3850405

3
Ogglas

Ich denke, die folgende Hilfsmethode könnte auch das Problem lösen.

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

Kann auf folgende Weise verwendet werden:

InvokeAsyncFuncSynchronously(Service.GetCustomersAsync);
1
donttellya

Ich habe festgestellt, dass SpinWait dafür ziemlich gut funktioniert.

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 muss nicht .Result oder .Wait () verwenden. Außerdem können Sie eine Zeitüberschreitung festlegen, damit Sie nicht für immer hängen bleiben, falls die Aufgabe nie abgeschlossen wird.

0
Curtis

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

Für eine aysnc-Methode geben Sie Task zurück.

Task DoSomeWork()

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

Bei asynchroner Methode Rückgabe Task<T>

Task<T> GetSomeValue()

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

Bearbeiten

Wenn der Aufrufer im Threading-Pool-Thread ausgeführt wird (oder der Aufrufer sich auch in einer Task befindet), kann dies in bestimmten Situationen trotzdem zu einem Deadlock führen. 

0
Liang