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

628

Ich lerne etwas über Async / Warten und bin auf eine Situation gestoßen, in der ich eine Async-Methode synchron aufrufen muss. Wie kann ich das machen?

Asynchrone Methode:

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

Normaler Gebrauch:

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

Ich habe versucht, Folgendes zu verwenden:

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 von hier aus versucht , aber er funktioniert nicht, wenn sich der Dispatcher in einem 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 die Stapelverfolgung beim Aufrufen 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()
Rachel
quelle
46
Die beste Antwort auf die Frage "Wie kann ich eine asynchrone Methode synchron aufrufen?" Ist "Nicht". Es gibt Hacks , um zu versuchen, es zum Funktionieren zu zwingen, aber alle haben sehr subtile Fallstricke. Sichern und korrigieren Sie stattdessen den Code, der Sie dazu "benötigt".
Stephen Cleary
57
@ Stephen Cleary Absolut einverstanden, aber manchmal ist es einfach unvermeidlich, z. B. wenn Ihr Code von einer Drittanbieter-API abhängig ist, die nicht async / await verwendet. Wenn bei Verwendung von MVVM eine Bindung an WPF-Eigenschaften erfolgt, ist die Verwendung von async / await buchstäblich unmöglich, da dies für Eigenschaften nicht unterstützt wird.
Contango
3
@ StephenCleary Nicht immer. Ich erstelle eine DLL, die in GeneXus importiert wird . Es werden keine asynchronen / wartenden Schlüsselwörter unterstützt, daher muss ich nur synchrone Methoden verwenden.
Dinei
5
@StephenCleary 1) GeneXus ist ein drittes pt-Tool und ich habe keinen Zugriff auf den Quellcode. 2) GeneXus hat nicht einmal Implementierungen von "Funktionen", daher kann ich nicht erkennen, wie ich mit solchen Dingen einen "Rückruf" implementieren könnte. Sicherlich wäre es eine schwierigere Problemumgehung als die Tasksynchrone Verwendung . 3) Ich integriere GeneXus in den MongoDB C # -Treiber , der einige Methoden nur asynchron
Dinei
1
@ygoe: Verwenden Sie eine asynchrone Kompatibilität, z SemaphoreSlim.
Stephen Cleary

Antworten:

456

Hier ist eine Problemumgehung, die in allen Fällen funktioniert (einschließlich suspendierter Disponenten). 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;
        }
    }
}
Rachel
quelle
28
Stephen Toub (Mr Parallel) hat eine Reihe von Beiträgen dazu verfasst, um Hintergrundinformationen darüber zu erhalten, wie dies funktioniert. Teil 1 Teil 2 Teil 3
Cameron MacFarland
18
Ich habe Johns Code so aktualisiert, dass er funktioniert, ohne Aufgaben in Lambdas zu verpacken: github.com/tejacques/AsyncBridge . Im Wesentlichen arbeiten Sie mit asynchronen Blöcken mit der using-Anweisung. Alles innerhalb eines using-Blocks geschieht asynchron, mit einer Wartezeit am Ende. Der Nachteil ist, dass Sie die Aufgabe selbst in einem Rückruf auspacken müssen, aber es ist immer noch ziemlich elegant, insbesondere wenn Sie mehrere asynchrone Funktionen gleichzeitig aufrufen müssen.
Tom Jacques
17
@StephenCleary Obwohl ich mit Ihnen einig , dass der Code async den ganzen Weg nach unten sein, manchmal hat man sich in einer Situation befinden , nicht durchführbar , wo man hat es als ein synchroner Aufruf zu erzwingen. Grundsätzlich ist meine Situation, dass mein gesamter Datenzugriffscode asynchron ist. Ich musste eine Sitemap basierend auf der Sitemap erstellen, und die von mir verwendete Drittanbieter-Bibliothek war MvcSitemap. Wenn man es nun über die DynamicNodeProviderBaseBasisklasse erweitert, kann man es nicht als asyncMethode deklarieren . Entweder musste ich durch eine neue Bibliothek ersetzen oder einfach eine synchrone Operation aufrufen.
justin.lovell
6
@ justin.lovell: Ja, Bibliotheksbeschränkungen können uns dazu zwingen, Hacks durchzuführen , zumindest bis die Bibliothek aktualisiert wird. Es hört sich so an, als ob MvcSitemap eine solche Situation ist, in der ein Hack erforderlich ist (auch MVC-Filter und untergeordnete Aktionen). Ich rate den Leuten generell davon ab, weil solche Hacks viel zu oft verwendet werden, wenn sie nicht notwendig sind. Insbesondere bei MVC gehen einige ASP.NET / MVC-APIs davon aus, dass sie eine haben AspNetSynchronizationContext, sodass dieser bestimmte Hack nicht funktioniert, wenn Sie diese APIs aufrufen.
Stephen Cleary
5
Dieser Code funktioniert nicht. Wenn es von einem Pool-Thread aufgerufen wird, kann dies einen Deadlock durch Thread-Hunger auslösen. Ihr Anrufer blockiert das Warten auf den Abschluss des Vorgangs. Dies kann niemals passieren, wenn er den Thread-Pool erschöpft hat. Siehe diesen Artikel .
ZunTzu
318

Bitte beachten Sie, dass diese Antwort drei Jahre alt ist. Ich habe es hauptsächlich aufgrund von Erfahrungen mit .Net 4.0 geschrieben und sehr wenig mit 4.5, insbesondere mit async-await. Im Allgemeinen ist es eine schöne einfache Lösung, aber manchmal bricht es Dinge. 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 , Task.Result , Task.RunSynchronously


.Net 4.0

Benutze das:

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

x.AsyncWaitHandle.WaitOne();

...oder dieses:

task.Start();
task.Wait();
AK_
quelle
67
.Resultkann in bestimmten Szenarien einen Deadlock erzeugen
Jordy Langen
122
Resultkann leicht zu einem Deadlock im asyncCode führen , wie ich in meinem Blog beschreibe.
Stephen Cleary
8
@StephenCleary Ich habe Ihren Beitrag gelesen und selbst ausprobiert. Ich denke ehrlich, jemand bei Microsoft war wirklich betrunken ... Es ist das gleiche Problem wie bei Winforms und Hintergrund-Threads ...
AK_
9
Die Frage betrifft eine Aufgabe, die von der asynchronen Methode zurückgegeben wird. Eine solche Art von Aufgabe haben bereits begonnen, ausgeführt oder abgebrochen, so Verwendung von Task.RunSynchronously Verfahren führen kann InvalidOperationException . Siehe MSDN-Seite: Task.RunSynchronously-Methode . Außerdem wird diese Aufgabe wahrscheinlich von den Methoden Task.Factory.StartNew oder Task.Run (innerhalb der asynchronen Methode) erstellt, sodass es gefährlich ist, sie erneut zu starten. Einige Rennbedingungen können zur Laufzeit auftreten. In der anderen Hand können Task.Wait und Task.Result zu einem Deadlock führen.
sgnsajgon
4
Synchron ausführen hat bei mir funktioniert ... Ich weiß nicht, ob mir etwas fehlt, aber dies scheint den Schrecken der markierten Antwort vorzuziehen zu sein. Ich habe nur nach einer Möglichkeit gesucht, Async auszuschalten, um Code zu testen, der nur zum Stoppen da ist die ui vom hängen
JonnyRaa
121

Ü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 ein AggregateException(like Result) geworfen.
  • funktioniert für beide Taskund Task<T>( probieren Sie es selbst aus! )

Da GetAwaiterdies vom Typ Ente ist, sollte dies auch für jedes Objekt funktionieren, das von einer asynchronen Methode (wie ConfiguredAwaitableoder YieldAwaitable) zurückgegeben wird, nicht nur für Aufgaben.


Bearbeiten: Bitte beachten Sie, dass dieser Ansatz (oder seine Verwendung .Result) zum Deadlock führen kann, es sei denn, Sie stellen sicher, dass Sie .ConfigureAwait(false)jedes Mal , wenn Sie warten, alle asynchronen Methoden hinzufügen , von denen aus möglicherweise erreicht werden kann BlahAsync()(nicht nur diejenigen, die direkt aufgerufen werden). 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, um .ConfigureAwait(false)überall etwas hinzuzufügen , und Sie sich nicht für die Leistung interessieren, können Sie dies alternativ tun

Task.Run(() => BlahAsync()).GetAwaiter().GetResult()
James Ko
quelle
1
Funktioniert für mich für einfache Sachen. Wenn die Methode eine IAsyncOperation zurückgibt, musste ich sie zuerst in eine Task konvertieren: BlahAsync (). AsTask (). GetAwaiter (). GetResult ();
Lee McPherson
3
Dies verursachte einen Deadlock innerhalb einer asmx-Webmethode. Das Umschließen des Methodenaufrufs in Task.Run () hat jedoch funktioniert: Task.Run (() => BlahAsync ()). GetAwaiter (). GetResult ()
Augusto Barreto
Ich mag diesen Ansatz am besten syntaktisch, weil er keine Lambdas beinhaltet.
Dythim
25
Bitte bearbeiten Sie NICHT die Antworten anderer Personen, um einen Link zu Ihren eigenen einzufügen. Wenn Sie glauben, dass Ihre Antwort besser ist, hinterlassen Sie sie stattdessen als Kommentar.
Rachel
1
docs.microsoft.com/en-us/dotnet/api/… sagt über GetAwaiter(): "Diese Methode ist für Compiler-Benutzer gedacht und nicht direkt im Code."
Theophilus
75

Es ist viel einfacher, die Aufgabe im Thread-Pool auszuführen, als den Scheduler dazu zu bringen, sie synchron auszuführen. Auf diese Weise können Sie sicher sein, dass es nicht zum Stillstand 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; 
Michael L Perry
quelle
3
Dann rufen Sie task.Wait () auf. Der Datentyp ist einfach Aufgabe.
Michael L Perry
1
Nehmen wir an, dass DoSomethingAsync () eine asynchrone Methode mit langer Laufzeit als Ganzes ist (intern wartet sie auf eine lange laufende Aufgabe), aber dem Aufrufer schnell eine Flusskontrolle zurückgibt, sodass die Arbeit mit dem Lambda-Argument auch schnell endet. Das Ergebnis von Tusk.Run () kann Task <Aufgabe> oder Task <Task <>> sein , sodass Sie auf ein Ergebnis der äußeren Aufgabe warten, das schnell erledigt wird, aber der inneren Aufgabe (aufgrund des Wartens auf einen lang laufenden Job in der asynchronen Methode). läuft noch. Die Schlussfolgerungen sind, dass wir wahrscheinlich den Unwrap () -Ansatz verwenden müssen (wie in @ J.Lennon post), um ein synchrones Verhalten der asynchronen Methode zu erreichen.
sgnsajgon
5
@sgnsajgon Du liegst falsch. Task.Run unterscheidet sich von Task.Factory.StartNew darin, dass das Ergebnis bereits automatisch ausgepackt wird. Siehe diesen Artikel .
ZunTzu
1
Kann ich Task.Run(DoSomethingAsync)stattdessen einfach schreiben ? Dadurch wird eine Ebene von Delegierten entfernt.
Ygoe
1
Ja. Es Task<MyResult> task = Task.Run(async () => await DoSomethingAsync());ist jedoch expliziter , in die entgegengesetzte Richtung zu gehen, da dies expliziter ist und die Besorgnis von @sgnsajgon anspricht, dass möglicherweise eine Aufgabe <Aufgabe <Mein Ergebnis >> zurückgegeben wird. Die richtige Überladung von Task.Run wird in beiden Fällen ausgewählt, aber der asynchrone Delegat macht Ihre Absicht offensichtlich.
Michael L Perry
57

Ich lerne etwas über Async / Warten und bin auf eine Situation gestoßen, in der ich eine Async-Methode synchron aufrufen muss. Wie kann ich das machen?

Die beste Antwort ist, dass Sie dies nicht tun. Die Details hängen von der "Situation" ab.

Ist es ein Property Getter / Setter? In den meisten Fällen ist es besser, asynchrone Methoden als "asynchrone Eigenschaften" zu haben. (Weitere Informationen finden Sie in meinem Blogbeitrag zu asynchronen Eigenschaften. )

Ist dies eine MVVM-App und möchten Sie eine asynchrone Datenbindung durchführen? Verwenden Sie dann so etwas wie my 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 Blogbeitrag zu asynchronen Konstruktoren. )

Es gibt fast immer eine bessere Antwort als Sync-over-Async.

Wenn es für Ihre Situation nicht möglich ist (und Sie dies wissen, indem Sie hier eine Frage stellen, die die Situation beschreibt ), würde ich empfehlen, nur synchronen Code zu verwenden. Async den ganzen Weg ist am besten; Die vollständige Synchronisierung ist die zweitbeste. Sync-over-Async wird nicht empfohlen.

Es gibt jedoch eine Handvoll Situationen, in denen eine Synchronisierung über Asynchronisierung erforderlich ist. Insbesondere werden Sie so durch den anrufenden Code eingeschränkt , dass Sie haben vollkommen synchron sein (und haben absolut keine Möglichkeit , zu überdenken , oder Re-Struktur Code Asynchronität zu ermöglichen), und Sie haben Asynchron - Code aufzurufen. Dies ist eine sehr seltene Situation, die jedoch von Zeit zu Zeit auftritt.

In diesem Fall müßten Sie eine der Hacks in meinem Artikel beschrieben verwenden Bricht asyncEntwicklung , insbesondere:

  • Blockieren (zB GetAwaiter().GetResult()). Beachten Sie, dass dies zu Deadlocks führen kann (wie ich in meinem Blog beschreibe).
  • Ausführen des Codes in einem Thread-Pool-Thread (z Task.Run(..).GetAwaiter().GetResult(). B. ). Beachten Sie, dass dies nur funktioniert, wenn der asynchrone Code in einem Thread-Pool-Thread ausgeführt werden kann (dh nicht von einer Benutzeroberfläche oder einem ASP.NET-Kontext abhängig ist).
  • Verschachtelte Nachrichtenschleifen. Beachten Sie, dass dies nur funktioniert, wenn der asynchrone Code nur einen Single-Thread-Kontext und keinen bestimmten Kontexttyp annimmt (viele Benutzeroberflächen- und ASP.NET-Codes erwarten einen bestimmten Kontext).

Verschachtelte Nachrichtenschleifen sind die gefährlichsten aller Hacks, da sie zu einem erneuten Eintritt führen . Der Wiedereintritt ist äußerst schwierig zu begründen, und (IMO) ist die Ursache für die meisten Anwendungsfehler unter Windows. Insbesondere wenn Sie sich im UI-Thread befinden und eine Arbeitswarteschlange blockieren (warten, bis die asynchrone Arbeit abgeschlossen ist), führt die CLR tatsächlich ein Nachrichtenpumpen für Sie durch - sie verarbeitet tatsächlich einige Win32-Nachrichten aus Ihrem Code . Oh, und Sie haben keine Ahnung, welche Botschaften - wenn Chris Brumme sagt: "Wäre es nicht großartig, genau zu wissen, was gepumpt wird? Leider ist das Pumpen eine schwarze Kunst, die jenseits des sterblichen Verständnisses liegt." Dann haben wir wirklich keine Hoffnung zu wissen.

Wenn Sie also einen 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 pumpen. Dies ist ein berechtigtes Problem, da sie wissen, dass es sehr schwierig ist Code zu schreiben, der angesichts von Wiedereintritten robust ist. "

Ja, so ist es. Es ist sehr schwer, Code zu schreiben, der angesichts von Wiedereintritten robust ist. Verschachtelte Nachrichtenschleifen zwingen Sie dazu, Code zu schreiben, der angesichts von Wiedereintritten robust ist. Aus diesem Grunde ist die akzeptierte (und meist upvoted) Antwort auf diese Frage ist sehr gefährlich in der Praxis.

Wenn Sie keine anderen Optionen mehr haben - Sie können Ihren Code nicht neu gestalten, Sie können ihn nicht so umstrukturieren, dass er asynchron ist - Sie werden durch unveränderlichen aufrufenden Code gezwungen, synchron zu sein - Sie können den nachgeschalteten Code nicht so ändern, dass er synchronisiert wird - Sie können nicht blockieren - Sie können den asynchronen Code nicht in einem separaten Thread ausführen - dann und nur dann sollten Sie in Betracht ziehen, die Wiedereintrittsfähigkeit zu übernehmen.

Wenn Sie sich in dieser Ecke befinden, würde ich empfehlen, etwas Dispatcher.PushFramefür WPF-Apps zu verwenden , Application.DoEventsfür WinForm-Apps eine Schleife zu verwenden und für den allgemeinen Fall meine eigene AsyncContext.Run.

Stephen Cleary
quelle
Stephen, es gibt noch eine sehr ähnliche Frage, auf die Sie auch eine großartige Antwort gegeben haben. Glaubst du, einer von ihnen kann als Duplikat geschlossen werden oder vielleicht eine Anfrage zusammenführen oder zuerst Meta aufrufen (da jedes q ~ 200.000 Aufrufe mit mehr als 200 Stimmen hat)? Vorschläge?
Alexei Levenkov
1
@AlexeiLevenkov: Ich fühle mich aus einigen Gründen nicht richtig dabei: 1) Die Antwort auf die verknüpfte Frage ist ziemlich veraltet. 2) Ich habe einen ganzen Artikel zu diesem Thema geschrieben , der meiner Meinung nach vollständiger ist als alle vorhandenen SO Q / A. 3) Die akzeptierte Antwort auf diese Frage ist äußerst beliebt. 4) Ich bin vehement gegen diese akzeptierte Antwort. Dies als Betrug zu schließen, wäre also ein Machtmissbrauch. Das Schließen als Dup davon (oder Zusammenführen) würde eine gefährliche Antwort noch mehr befähigen. Ich lasse es sein und überlasse es der Gemeinschaft.
Stephen Cleary
OK. Ich werde darüber nachdenken, es auf Meta zu bringen, als auf irgendeine Weise.
Alexei Levenkov
9
Diese Antwort geht mir weit über den Kopf. "Verwenden Sie Async ganz nach unten" ist ein verwirrender Ratschlag, da es eindeutig nicht möglich ist, ihm zu folgen. Ein Programm mit einer asynchronen Main()Methode wird nicht kompiliert. an einem gewissen Punkt haben Sie bekommen die Lücke zwischen den sync und async Welten zu überbrücken. Dies ist keine " sehr seltene Situation" , sondern in buchstäblich jedem Programm erforderlich, das eine asynchrone Methode aufruft. Es gibt keine Option, nicht "Sync-over-Async" durchzuführen , sondern nur eine Option, um diese Belastung auf die aufrufende Methode zu verlagern, anstatt sie in diejenige zu übernehmen, die Sie gerade schreiben.
Mark Amery
1
Großartig. Ich werde asyncjetzt alle Methoden in meiner Anwendung anwenden. Und das ist viel. Kann das nicht einfach die Standardeinstellung sein?
Ygoe
25

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 möchten diesen Thread tatsächlich synchron blockieren , bis die asynchrone Methode abgeschlossen ist.

Asynchrone Methoden in C # 5 werden unterstützt, indem die Methode effektiv unter der Haube in Stücke Taskgeschnitten und eine zurückgegeben wird , die den Gesamtabschluss des gesamten Shabang verfolgen kann. Wie die zerhackten Methoden ausgeführt werden, hängt jedoch vom Typ des an den awaitOperator übergebenen Ausdrucks ab .

Meistens verwenden Sie awaiteinen Ausdruck vom Typ Task. Die Implementierung des awaitMusters durch Task ist insofern "klug", als es sich von dem unterscheidet SynchronizationContext, was im Grunde Folgendes bewirkt:

  1. Wenn sich der Thread, der in awaiteingeht, in einem Dispatcher- oder WinForms-Nachrichtenschleifenthread befindet, wird sichergestellt, dass die Blöcke der asynchronen Methode im Rahmen der Verarbeitung der Nachrichtenwarteschlange auftreten.
  2. Wenn sich der Thread, der in eingeht, in awaiteinem Thread-Pool-Thread befindet, treten die verbleibenden Blöcke der asynchronen Methode an einer beliebigen Stelle im Thread-Pool auf.

Aus diesem Grund treten wahrscheinlich Probleme auf - die Implementierung der asynchronen Methode versucht, den Rest auf dem Dispatcher auszuführen - obwohl er ausgesetzt ist.

.... sichern! ....

Ich muss die Frage stellen, warum Sie versuchen, eine asynchrone Methode synchron zu blockieren. Dies würde den Zweck zunichte machen, warum die Methode asynchron aufgerufen werden wollte. Wenn Sie mit der Verwendung awaiteiner Dispatcher- oder UI-Methode beginnen, möchten Sie im Allgemeinen Ihren gesamten UI-Flow asynchronisieren. Zum Beispiel, wenn Ihr Callstack ungefähr so ​​war:

  1. [Oben] WebRequest.GetResponse()
  2. YourCode.HelperMethod()
  3. YourCode.AnotherMethod()
  4. YourCode.EventHandlerMethod()
  5. [UI Code].Plumbing()- WPFoder WinFormsCode
  6. [Nachrichtenschleife] - WPFoder WinFormsNachrichtenschleife

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

  1. [Oben] WebRequest.GetResponseAsync()
  2. YourCode.HelperMethodAsync()
  3. YourCode.AnotherMethodAsync()
  4. YourCode.EventHandlerMethodAsync()
  5. [UI Code].Plumbing()- WPFoder WinFormsCode
  6. [Nachrichtenschleife] - WPFoder WinFormsNachrichtenschleife

Eigentlich 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, Ihre 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 mit dem CTP benötigen Sie die Ex-Suffixe ( Erklärung hier ).

Theo Yaung
quelle
+1 für die detaillierte Erklärung, TaskEx.RunEx(GetCustomers).Resulthängt jedoch die Anwendung, wenn sie auf einem angehaltenen Dispatcher-Thread ausgeführt wird. Außerdem wird die GetCustomers () -Methode normalerweise asynchron ausgeführt. In einer Situation muss sie jedoch synchron ausgeführt werden. Daher habe ich nach einer Möglichkeit gesucht, dies zu tun, ohne eine Synchronisierungsversion der Methode zu erstellen.
Rachel
+1 für "Warum versuchen Sie, eine asynchrone Methode synchron zu blockieren?" Es gibt immer eine Möglichkeit, asyncMethoden richtig anzuwenden. verschachtelte Schleifen sollten auf jeden Fall vermieden werden.
Stephen Cleary
24

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;
    }
}
Clement
quelle
Sie müssen auch die Task.Unwrap- Methode verwenden, da Ihre Task.Wait- Anweisung das Warten auf die äußere Task (erstellt von Task.Run ) und nicht auf das innere Warten verursacht. T Task wird als Parameter der Erweiterungsmethode übergeben. Ihre Task.Run- Methode gibt nicht Task <T>, sondern Task <Task <T>> zurück. In einigen einfachen Szenarien Ihre Lösung kann Werke wegen Taskscheduler Optimierungen, beispielsweise mit TryExecuteTaskInline Methode auszuführen Aufgaben innerhalb aktuellen Thread während Warten Betrieb .Please Blick auf meinen Kommentar zu dieser Antwort.
sgnsajgon
1
Das ist nicht richtig. Der Task.Run gibt Task <T> zurück. Siehe diese Überladung msdn.microsoft.com/en-us/library/hh194918(v=vs.110).aspx
Clement
Wie soll das verwendet werden? Diese Deadlocks in WPF:MyAsyncMethod().RunTaskSynchronously();
Ygoe
18

Der einfachste Weg, eine Aufgabe synchron und ohne Blockierung des UI-Threads auszuführen, besteht darin, RunSynchronously () wie folgt zu verwenden:

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

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

Pixel
quelle
1
Aber wie können wir diese Methode verwenden, wenn der asynchrone Code etwas zurückgibt, das wir benötigen?
Serpooshan
16

Ich habe mich ein paar Mal damit auseinandergesetzt, hauptsächlich in Unit-Tests oder in einer Windows-Service-Entwicklung. Derzeit benutze 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, leicht und ich hatte keine Probleme.

J. Lennon
quelle
Dies ist der einzige, der für mich nicht festgefahren ist.
AndreFeijo
15

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

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: Diese Methode ist nicht threadsicher, wenn Sie Ihre Methode innerhalb einer anderen asynchronen Methode aufrufen (gemäß den Kommentaren von @Servy).

RredCat
quelle
Ich habe diese Lösung erklärt, siehe Abschnitt BEARBEITEN.
RredCat
2
Dies kann sehr leicht zu Deadlocks führen, wenn es in asynchronen Situationen aufgerufen wird.
Servy
@Servy macht Sinn. Wenn ich also mit Wait (timeOut) richtig bin, kann das helfen, oder?
RredCat
1
Dann müssen Sie sich Sorgen machen, dass das Zeitlimit erreicht wird, wenn die Operation nicht tatsächlich ausgeführt wird, was sehr schlecht ist, und auch die Zeit, die Sie in den Fällen, in denen es blockiert, bis zum Zeitlimit warten müssen (und in diesem Fall fahren Sie immer noch fort) an, wenn es nicht fertig ist). Also nein, das behebt das Problem nicht.
Servy
@Servy Sieht so aus, als müsste ich CancellationTokenfür meine Lösung implementieren .
RredCat
10

Warten Sie in Ihrem Code zum ersten Mal auf die Ausführung der Aufgabe, aber Sie haben sie noch nicht gestartet, sodass sie auf unbestimmte Zeit wartet. 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);
}

Überprüfen Sie, ob dies für Sie funktioniert. Wenn dies nicht der Fall ist, obwohl dies sehr unwahrscheinlich ist, haben Sie möglicherweise einen seltsamen Build von Async CTP. Wenn dies funktioniert, möchten Sie möglicherweise untersuchen, was genau der Compiler generiert und wie sich die TaskInstanziierung von diesem Beispiel unterscheidet.

Edit # 2:

Ich habe mit Reflektor , dass die Ausnahme Sie tritt beschrieben , wenn m_actionist null. Das ist etwas seltsam, aber ich bin kein Experte für Async CTP. Wie gesagt, Sie sollten Ihren Code dekompilieren und sehen, wie genau Taskinstanziiert wird, wie es m_actionkommt null.


PS Was ist mit den gelegentlichen Abstimmungen los? Möchtest du das näher erläutern?

Dan Abramov
quelle
Ich habe meine Frage angepasst, um den Code, den ich versucht hatte, etwas klarer zu machen. RunSynchronously gibt einen Fehler von zurück RunSynchronously may not be called on a task unbound to a delegate. Google ist keine Hilfe, da alle Ergebnisse dafür auf Chinesisch sind ...
Rachel
Ich denke, der Unterschied ist, dass ich die Aufgabe nicht erstelle und dann versuche, sie auszuführen. Stattdessen wird die Aufgabe von der asynchronen Methode erstellt, wenn das awaitSchlüsselwort verwendet wird. Die in meinem früheren Kommentar veröffentlichte Ausnahme ist die Ausnahme, die ich erhalte, obwohl es eine der wenigen ist, für die ich nicht googeln und eine Ursache oder Lösung finden kann.
Rachel
1
asyncund asyncSchlüsselwörter sind nichts anderes als Syntaxzucker. Compiler generiert Code erstellen Task<Customer>in GetCustomers()so das ist , wo ich zum ersten Mal aussehen würde. Als Ausnahme haben Sie nur eine Ausnahmemeldung gepostet, die ohne Ausnahmetyp und Stapelverfolgung unbrauchbar ist. Rufen Sie die Ausnahmemethode auf ToString()und veröffentlichen Sie die Ausgabe in der Frage.
Dan Abramov
@gaearon: Ich habe die Ausnahmedetails und die Stapelverfolgung in meiner ursprünglichen Frage veröffentlicht.
Rachel
2
@gaearon Ich denke, Sie hatten Downvotes, weil Ihr Beitrag nicht auf Fragen anwendbar ist. In der Diskussion geht es um asynchrone Methoden, nicht um einfache Methoden, die Aufgaben zurückgeben. Darüber hinaus ist der Async-Wait-Mechanismus meiner Meinung nach ein Syntaxzucker, aber nicht so trivial - es gibt Fortsetzung, Kontexterfassung, Wiederaufnahme des lokalen Kontexts, verbesserte Behandlung lokaler Ausnahmen und vieles mehr. Dann sollten Sie die RunSynchronously- Methode nicht als Ergebnis der asynchronen Methode aufrufen , da die asynchrone Methode per Definition eine Task zurückgeben sollte, die derzeit mindestens geplant ist und sich mehr als einmal im laufenden Zustand befindet.
sgnsajgon
9

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

Für die Rückgabe der asynchronen Methode Task.

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

Für die Rückgabe der asynchronen Methode Task<T>

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

Bearbeiten :

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

Liang
quelle
1
Meine Antwort nach fast 8 Jahren :) Das zweite Beispiel - erzeugt einen Deadlock in allen geplanten Kontexten, die hauptsächlich verwendet werden (Konsolen-App / .NET-Kern / Desktop-App / ...). Hier haben Sie mehr Überblick, wovon ich gerade spreche: medium.com/rubrikkgroup/…
W92
Resultist perfekt für den Job, wenn Sie einen synchronen Anruf wünschen, und ansonsten geradezu gefährlich. Der Name Resultoder die Intelligenz enthält nichts, was Resultdarauf hinweist, dass es sich um einen blockierenden Anruf handelt. Es sollte wirklich umbenannt werden.
Zodman
5

Verwenden Sie den folgenden Code-Snip

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

Warum nicht einen Anruf erstellen wie:

Service.GetCustomers();

das ist nicht asynchron.

Daniel A. White
quelle
4
Das werde ich tun, wenn ich das nicht zum Laufen bringen kann ... erstelle eine Sync-Version zusätzlich zu einer Async-Version
Rachel
3

Diese Antwort richtet sich an alle, die WPF für .NET 4.5 verwenden.

Wenn Sie versuchen, Task.Run()auf dem GUI-Thread auszuführen , task.Wait()bleibt dies auf unbestimmte Zeit hängen, wenn Sie das asyncSchlüsselwort nicht in Ihrer Funktionsdefinition haben.

Diese Erweiterungsmethode löst das Problem, indem überprüft wird, ob sich der GUI-Thread befindet, und wenn ja, die Aufgabe im WPF-Dispatcher-Thread ausgeführt wird.

Diese Klasse kann als Bindeglied zwischen der asynchronen / wartenden Welt und der nicht asynchronen / wartenden Welt fungieren, wenn dies unvermeidbar ist, z. B. MVVM-Eigenschaften oder Abhängigkeiten von anderen APIs, die keine asynchrone / wartende Welt 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();
        }
    }
}
Contango
quelle
3

Einfach anrufen .Result;oder .Wait()ein Risiko für Deadlocks darstellen, wie viele in Kommentaren gesagt haben. Da die meisten von uns Oneliner mögen, können Sie diese für verwenden.Net 4.5<

Erfassen eines Werts über eine asynchrone Methode:

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

Synchrones Aufrufen einer asynchronen Methode

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

Durch die Verwendung von treten keine Deadlock-Probleme auf Task.Run.

Quelle:

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

Ogglas
quelle
1

Ich denke, die folgende Hilfsmethode könnte das Problem ebenfalls 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 folgendermaßen verwendet werden:

InvokeAsyncFuncSynchronously(Service.GetCustomersAsync);
donttellya
quelle
1
Bitte erklären Sie die Abstimmung
donttellya
2
... ich bin immer noch gespannt, warum diese Antwort abgelehnt wurde?
Donttellya
Es ist kein echtes "synchron". Sie erstellen zwei Threads und warten auf die ersten Ergebnisse anderer.
tmt
Abgesehen davon ist dies eine sehr schlechte Idee.
Dan Pantry
1
Ich habe gerade fast den gleichen Code geschrieben (Zeile für Zeile gleich), aber stattdessen SemaphoreSlim anstelle des Ereignisses für das automatische Zurücksetzen verwendet. Ich wünschte, ich hätte das früher gesehen. Ich finde diesen Ansatz, um Deadlocks zu verhindern und Ihren asynchronen Code so auszuführen, wie er in echten asynchronen Szenarien ausgeführt wird. Ich bin mir nicht sicher, warum das eine schlechte Idee ist. Scheint viel sauberer als die anderen Ansätze, die ich oben gesehen habe.
Tmrog
0

Das funktioniert bei mir

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();
        }
    }
}
Dan Nguyen
quelle
-1

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 ein Zeitlimit festlegen, damit Sie nicht für immer hängen bleiben, falls die Aufgabe nie abgeschlossen wird.

Curtis
quelle
1
Die Abwertung deutet darauf hin, dass jemand diese Methode nicht mag. Gibt es jemanden, der den Nachteil kommentieren kann?
Grax32
Kann jemand, wenn der Downvoter nicht sagt, WARUM das Downvote abgegeben wurde, es upvoten? :-)
Curtis
1
Dies ist eine Abfrage (Drehung). Der Delegat nimmt bis zu 1000 Mal pro Sekunde Thread aus dem Pool. Möglicherweise wird die Steuerung nicht sofort nach Beendigung der Aufgabe zurückgegeben (bis zu 10 + ms Fehler). Wenn das Zeitlimit abgelaufen ist, wird die Aufgabe weiter ausgeführt, wodurch das Zeitlimit praktisch unbrauchbar wird.
Sinatr
Eigentlich verwende ich dies überall in meinem Code und wenn die Bedingung erfüllt ist, wird SpinWaitSpinUntil () sofort beendet. Je nachdem, was zuerst eintritt, "Bedingung erfüllt" oder Zeitüberschreitung, wird die Aufgabe beendet. Es läuft nicht weiter.
Curtis
-3

Auf wp8:

Wickeln Sie es ein:

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

Nennen:

GetCustomersSynchronously();
user2113284
quelle
3
Nein, das wird nicht funktionieren, da die Aufgabe nicht auf den Delegierten vom Konstruktor wartet (es ist ein Delegat und keine Aufgabe ..)
Rico Suter
-4
    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;
        }
    }
ksemenenko
quelle
-5

Oder Sie könnten einfach gehen mit:

customerList = Task.Run<List<Customer>>(() => { return GetCustomers(); }).Result;

Stellen Sie zum Kompilieren sicher, dass Sie auf die Erweiterungsbaugruppe verweisen:

System.Net.Http.Formatting
user2057962
quelle
-9

Versuchen Sie folgenden Code, der für mich funktioniert:

public async void TaskSearchOnTaskList (SearchModel searchModel)
{
    try
    {
        List<EventsTasksModel> taskSearchList = await Task.Run(
            () => MakeasyncSearchRequest(searchModel),
            cancelTaskSearchToken.Token);

        if (cancelTaskSearchToken.IsCancellationRequested
                || string.IsNullOrEmpty(rid_agendaview_search_eventsbox.Text))
        {
            return;
        }

        if (taskSearchList == null || taskSearchList[0].result == Constants.ZERO)
        {
            RunOnUiThread(() => {
                textViewNoMembers.Visibility = ViewStates.Visible;                  
                taskListView.Visibility = ViewStates.Gone;
            });

            taskSearchRecureList = null;

            return;
        }
        else
        {
            taskSearchRecureList = TaskFooterServiceLayer
                                       .GetRecurringEvent(taskSearchList);

            this.SetOnAdapter(taskSearchRecureList);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("ActivityTaskFooter -> TaskSearchOnTaskList:" + ex.Message);
    }
}
Gandhraj Gayakwad
quelle