Wie rufe ich eine asynchrone Methode von einer synchronen Methode in C # auf?


863

Ich habe ein public async void Foo() Methode, die ich von der synchronen Methode aufrufen möchte. Bisher habe ich in der MSDN-Dokumentation nur gesehen, dass asynchrone Methoden über asynchrone Methoden aufgerufen werden, aber mein gesamtes Programm ist nicht mit asynchronen Methoden erstellt.

Ist das überhaupt möglich?

Hier ist ein Beispiel für den Aufruf dieser Methoden von einer asynchronen Methode: http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx

Jetzt möchte ich diese asynchronen Methoden von Synchronisationsmethoden aus aufrufen.


2
Ich bin auch darauf gestoßen. Wenn Sie einen RoleProvider überschreiben, können Sie die Methodensignatur der GetRolesForUser-Methode nicht ändern, sodass Sie die Methode nicht asynchronisieren und also nicht warten können, um die API asynchron aufzurufen. Meine vorübergehende Lösung bestand darin, meiner generischen HttpClient-Klasse synchrone Methoden hinzuzufügen, möchte aber wissen, ob dies möglich ist (und welche Auswirkungen dies haben könnte).
Timothy Lee Russell

1
Da Ihre async void Foo()Methode a nicht zurückgibt Task, bedeutet dies, dass ein Aufrufer nicht wissen kann, wann sie abgeschlossen ist, sondern Taskstattdessen zurückkehren muss .
Dai

1
Verknüpfen einer verwandten Frage / Antwort dazu in einem UI-Thread.
Noseratio

Antworten:


711

Die asynchrone Programmierung "wächst" durch die Codebasis. Es wurde mit einem Zombie-Virus verglichen . Die beste Lösung ist, es wachsen zu lassen, aber manchmal ist das nicht möglich.

Ich habe einige Typen in meine Nito.AsyncEx- Bibliothek geschrieben, um mit einer teilweise asynchronen Codebasis umzugehen . Es gibt jedoch keine Lösung, die in jeder Situation funktioniert.

Lösung A.

Wenn Sie eine einfache asynchrone Methode haben, die nicht wieder mit ihrem Kontext synchronisiert werden muss, können Sie Folgendes verwenden Task.WaitAndUnwrapException:

var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();

Sie möchten nicht verwenden Task.Waitoder Task.Resultweil sie Ausnahmen einschließen AggregateException.

Diese Lösung ist nur geeignet, wenn MyAsyncMethodsie nicht mit ihrem Kontext synchronisiert wird. Mit anderen Worten, jedes awaitIn MyAsyncMethodsollte mit enden ConfigureAwait(false). Dies bedeutet, dass keine UI-Elemente aktualisiert oder auf den ASP.NET-Anforderungskontext zugegriffen werden kann.

Lösung B.

Wenn MyAsyncMethodeine Synchronisierung mit dem Kontext erforderlich ist, können Sie möglicherweise AsyncContext.RunTaskeinen verschachtelten Kontext bereitstellen:

var result = AsyncContext.RunTask(MyAsyncMethod).Result;

* Update 14.04.2014: In neueren Versionen der Bibliothek lautet die API wie folgt:

var result = AsyncContext.Run(MyAsyncMethod);

( Task.ResultIn diesem Beispiel ist es in Ordnung, da RunTaskes sich ausbreitetTask Ausnahmen).

Der Grund, den Sie möglicherweise AsyncContext.RunTaskanstelle von benötigen, Task.WaitAndUnwrapExceptionist eine ziemlich subtile Deadlock-Möglichkeit, die unter WinForms / WPF / SL / ASP.NET auftritt:

  1. Eine synchrone Methode ruft eine asynchrone Methode auf und erhält a Task.
  2. Die synchrone Methode wartet blockierend auf die Task.
  3. Die asyncMethode verwendetawait ohne ConfigureAwait.
  4. Das Taskkann in dieser Situation nicht abgeschlossen werden, da es erst abgeschlossen wird, wenn die asyncMethode abgeschlossen ist. Die asyncMethode kann nicht abgeschlossen werden, da versucht wird, die Fortsetzung auf die zu planenSynchronizationContext , und WinForms / WPF / SL / ASP.NET lässt die Fortsetzung nicht zu, da die synchrone Methode bereits in diesem Kontext ausgeführt wird.

Dies ist ein Grund, warum es eine gute Idee ist, so viel wie möglich ConfigureAwait(false)in jeder asyncMethode zu verwenden .

Lösung C.

AsyncContext.RunTaskfunktioniert nicht in jedem Szenario. Wenn die asyncMethode beispielsweise auf etwas wartet, für dessen Abschluss ein UI-Ereignis erforderlich ist, blockieren Sie auch mit dem verschachtelten Kontext. In diesem Fall können Sie die asyncMethode im Thread-Pool starten :

var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();

Diese Lösung erfordert jedoch eine MyAsyncMethod, die im Thread-Pool-Kontext funktioniert. Daher kann es keine UI-Elemente aktualisieren oder auf den ASP.NET-Anforderungskontext zugreifen. Und in diesem Fall können Sie auch hinzufügen , ConfigureAwait(false)um seine awaitAussagen und Lösung A. verwenden

Update, 01.05.2019: Die aktuellen "am wenigsten schlimmsten Praktiken" finden Sie in einem MSDN-Artikel hier .


9
Lösung A scheint das zu sein, was ich will, aber es sieht nach Aufgabe aus. WaitAndUnwrapException () hat es nicht in die .Net 4.5 RC geschafft. es hat nur task.Wait (). Irgendeine Idee, wie man das mit der neuen Version macht? Oder ist dies eine benutzerdefinierte Erweiterungsmethode, die Sie geschrieben haben?
tödlicher Hund

3
WaitAndUnwrapExceptionist meine eigene Methode aus meiner AsyncEx-Bibliothek . Die offiziellen .NET-Bibliotheken bieten keine große Hilfe beim Mischen von Synchronisations- und Async-Code (und im Allgemeinen sollten Sie dies nicht tun!). Ich warte auf .NET 4.5 RTW und einen neuen Nicht-XP-Laptop, bevor ich AsyncEx auf 4.5 aktualisiere (ich kann derzeit nicht für 4.5 entwickeln, da ich noch einige Wochen auf XP stecke).
Stephen Cleary

12
AsyncContexthat jetzt eine RunMethode, die einen Lambda-Ausdruck nimmt, also sollten Sie verwendenvar result = AsyncContext.Run(() => MyAsyncMethod());
Stephen Cleary

1
Ich habe Ihre Bibliothek von Nuget entfernt, aber es scheint keine RunTaskMethode zu geben. Das nächste, was ich finden konnte, war Run, aber das hat keine ResultEigenschaft.
Asad Saeeduddin

3
@Asad: Ja, mehr als 2 Jahre später hat sich die API geändert. Sie können jetzt einfach sagenvar result = AsyncContext.Run(MyAsyncMethod);
Stephen Cleary

313

Das Hinzufügen einer Lösung, die mein Problem endlich gelöst hat, spart hoffentlich jemandem Zeit.

Lesen Sie zuerst ein paar Artikel von Stephen Cleary :

Von den "zwei Best Practices" in "Nicht auf asynchronem Code blockieren" hat die erste für mich nicht funktioniert und die zweite war nicht anwendbar (im Grunde genommen, wenn ich sie verwenden kann await!).

Hier ist meine Problemumgehung: Wickeln Sie den Anruf in einen Task.Run<>(async () => await FunctionAsync());und hoffentlich keinen Deadlock mehr.

Hier ist mein Code:

public class LogReader
{
    ILogger _logger;

    public LogReader(ILogger logger)
    {
        _logger = logger;
    }

    public LogEntity GetLog()
    {
        Task<LogEntity> task = Task.Run<LogEntity>(async () => await GetLogAsync());
        return task.Result;
    }

    public async Task<LogEntity> GetLogAsync()
    {
        var result = await _logger.GetAsync();
        // more code here...
        return result as LogEntity;
    }
}

5
Zwei Jahre später bin ich gespannt, wie sich diese Lösung hält. Irgendwelche Neuigkeiten? Gibt es eine Subtilität in diesem Ansatz, die für Neulinge verloren geht?
Dan Esparza

26
Dies wird zwar nicht zum Stillstand kommen, sondern nur, weil es gezwungen ist, in einem neuen Thread außerhalb des Synchronisationskontexts des ursprünglichen Threads ausgeführt zu werden. Es gibt jedoch bestimmte Umgebungen, in denen dies sehr schlecht beraten ist: insbesondere Webanwendungen. Dies könnte die verfügbaren Threads für den Webserver effektiv halbieren (ein Thread für die Anforderung und einer für diese). Je mehr Sie dies tun, desto schlimmer wird es. Sie könnten möglicherweise Ihren gesamten Webserver blockieren.
Chris Pratt

30
@ChrisPratt - Möglicherweise haben Sie Recht, da dies Task.Run()in einem asynchronen Code keine bewährte Methode ist. Aber wie lautet die Antwort auf die ursprüngliche Frage? Nie eine asynchrone Methode synchron aufrufen? Wir wünschen, aber in einer realen Welt müssen wir manchmal.
Tohid

1
@Tohid Sie könnten Stephen Clearys Bibliothek versuchen. Ich habe gesehen, wie Leute davon ausgegangen sind und Parallel.ForEachMissbrauch in der "realen Welt" keine Auswirkungen hat und schließlich die Server heruntergefahren hat. Dieser Code ist für Konsolen-Apps in Ordnung, sollte jedoch, wie @ChrisPratt sagt, nicht in Web-Apps verwendet werden. Es könnte "jetzt" funktionieren, ist aber nicht skalierbar.
Makhdumi

1
Ich bin fasziniert davon, neue Konten zu erstellen, indem ich Fragen beantworte, um genügend Punkte zu erhalten, um diese zu verbessern ...
Giannis Paraskevopoulos

206

Microsoft hat eine (interne) AsyncHelper-Klasse erstellt, um Async als Sync auszuführen. Die Quelle sieht aus wie:

internal static class AsyncHelper
{
    private static readonly TaskFactory _myTaskFactory = new 
      TaskFactory(CancellationToken.None, 
                  TaskCreationOptions.None, 
                  TaskContinuationOptions.None, 
                  TaskScheduler.Default);

    public static TResult RunSync<TResult>(Func<Task<TResult>> func)
    {
        return AsyncHelper._myTaskFactory
          .StartNew<Task<TResult>>(func)
          .Unwrap<TResult>()
          .GetAwaiter()
          .GetResult();
    }

    public static void RunSync(Func<Task> func)
    {
        AsyncHelper._myTaskFactory
          .StartNew<Task>(func)
          .Unwrap()
          .GetAwaiter()
          .GetResult();
    }
}

Die Microsoft.AspNet.Identity-Basisklassen verfügen nur über Async-Methoden. Um sie als Sync aufzurufen, gibt es Klassen mit Erweiterungsmethoden, die wie folgt aussehen (Beispielverwendung):

public static TUser FindById<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
    if (manager == null)
    {
        throw new ArgumentNullException("manager");
    }
    return AsyncHelper.RunSync<TUser>(() => manager.FindByIdAsync(userId));
}

public static bool IsInRole<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId, string role) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
    if (manager == null)
    {
        throw new ArgumentNullException("manager");
    }
    return AsyncHelper.RunSync<bool>(() => manager.IsInRoleAsync(userId, role));
}

Für diejenigen, die sich Gedanken über die Lizenzierungsbedingungen für Code machen, ist hier ein Link zu sehr ähnlichem Code (fügt nur Unterstützung für die Kultur im Thread hinzu), der Kommentare enthält, die darauf hinweisen, dass es sich um eine von Microsoft lizenzierte MIT handelt. https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs


2
Meine asynchronen Methoden warten auf andere asynchrone Methoden. Ich dekoriere KEINE meiner awaitAnrufe mit ConfigureAwait(false). Ich habe versucht AsyncHelper.RunSync, eine asynchrone Funktion aus der Application_Start()Funktion in Global.asax aufzurufen, und es scheint zu funktionieren. Bedeutet dies, dass dies AsyncHelper.RunSyncnicht anfällig für das Deadlock-Problem "Marschall zurück zum Kontext des Anrufers" ist, über das ich an anderer Stelle in diesem Beitrag gelesen habe?
Bob.at.Indigo.Health

1
@ Bob.at.SBS hängt davon ab, was Ihr Code tut. Es ist nicht so einfach, als ob ich diesen Code benutze, bin ich sicher . Dies ist eine sehr minimale und halb sichere Methode, um asynchrone Befehle synchron auszuführen. Sie kann leicht unangemessen verwendet werden, um Deadlocks zu verursachen.
Erik Philips

1
Vielen Dank. 2 Anschlussfragen: 1) Können Sie ein Beispiel für etwas geben, das die asynchrone Methode vermeiden möchte und das zu einem Deadlock führen würde, und 2) sind Deadlocks in diesem Zusammenhang häufig zeitabhängig? Wenn es in der Praxis funktioniert, lauert möglicherweise immer noch ein zeitabhängiger Deadlock in meinem Code?
Bob.at.Indigo.Health

@ Bob.at.SBS Ich würde empfehlen, Fragen zu stellen, indem Sie die Schaltfläche Frage stellen oben rechts verwenden. Sie können einen Link zu dieser Frage oder Antwort als Referenz in Ihre Frage aufnehmen.
Erik Philips

1
@ Bob.at ... der von Erik bereitgestellte Code funktioniert perfekt unter Asp. net mvc5 und EF6, aber nicht, wenn ich eine der anderen Lösungen ausprobiert habe (ConfigureAwait (false) .GetAwaiter (). GetResult () oder .result), die meine Web-App vollständig hängen lassen
LeonardoX

151

async Main ist jetzt Teil von C # 7.2 und kann in den erweiterten Build-Einstellungen des Projekts aktiviert werden.

Für C # <7.2 lautet der richtige Weg:

static void Main(string[] args)
{
   MainAsync().GetAwaiter().GetResult();
}


static async Task MainAsync()
{
   /*await stuff here*/
}

Sie werden dies in vielen Microsoft-Dokumentationen sehen, zum Beispiel: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use- Themen-Abonnements


11
Ich habe keine Ahnung, warum jemand dies abgelehnt hat. Das hat bei mir super funktioniert. Ohne dieses Update hätte ich ASYCH ÜBERALL verbreiten müssen.
Gefangener NULL

11
Warum ist das besser als MainAsync().Wait()?
Crush

8
Genau. Sie brauchen nur MainAsync (). Wait () statt all dem.
Hajjat

8
@crush Ich habe beschrieben, wie dies einige Deadlocks vermeiden kann. In einigen Situationen führt das Aufrufen von .Wait () über eine Benutzeroberfläche oder einen asp.net-Thread zu einem Deadlock. asynchrone Deadlocks
David

6
@ClintB: Sie sollten dies in ASP.NET Core unbedingt nicht tun. Webanwendungen sind besonders anfällig für Thread-Mangel. Jedes Mal, wenn Sie dies tun, ziehen Sie einen Thread aus dem Pool, der sonst zur Bearbeitung einer Anforderung verwendet würde. Für Desktop- / Mobilanwendungen ist dies weniger problematisch, da sie traditionell Einzelbenutzer sind.
Chris Pratt

52
public async Task<string> StartMyTask()
{
    await Foo()
    // code to execute once foo is done
}

static void Main()
{
     var myTask = StartMyTask(); // call your method which will return control once it hits await
     // now you can continue executing code here
     string result = myTask.Result; // wait for the task to complete to continue
     // use result

}

Sie lesen das Schlüsselwort 'await' als "Diese lange laufende Aufgabe starten und dann die Steuerung an die aufrufende Methode zurückgeben". Sobald die lang laufende Aufgabe erledigt ist, führt sie den Code danach aus. Der Code nach dem Warten ähnelt den früheren CallBack-Methoden. Der große Unterschied besteht darin, dass der logische Ablauf nicht unterbrochen wird, was das Schreiben und Lesen erheblich erleichtert.


15
Waitschließt Ausnahmen aus und hat die Möglichkeit eines Deadlocks.
Stephen Cleary

Ich dachte, wenn Sie eine asynchrone Methode ohne Verwendung aufrufen awaitwürden, würde sie synchron ausgeführt. Zumindest funktioniert das bei mir (ohne anzurufen myTask.Wait). Eigentlich habe ich eine Ausnahme bekommen, als ich versucht habe anzurufen, myTask.RunSynchronously()weil es bereits ausgeführt wurde!
Ehrfurcht

2
Ich mag diese Antwort. Gute Kommentare zum Bearbeiten, klein und elegant. Vielen Dank für Ihren Beitrag! Ich lerne immer noch Parallelität, also hilft alles :)
kayleeFrye_onDeck

2
Sollte diese Antwort bis heute noch funktionieren? Ich habe es gerade in einem MVC Razor-Projekt versucht und die App hängt nur beim Zugriff .Result.
Gone Coding

8
@TrueBlueAussie Das ist der Deadlock für den Synchronisationskontext. Ihr asynchroner Code wird wieder in den Synchronisationskontext versetzt, der jedoch zu diesem ResultZeitpunkt durch den Aufruf blockiert wird , sodass er nie dort ankommt. Und Resultendet nie, weil es auf jemanden wartet, der Resultauf das Ende wartet , im Grunde: D
Luaan

40

Ich bin nicht 100% sicher, aber ich glaube, dass die in diesem Blog beschriebene Technik unter vielen Umständen funktionieren sollte:

Sie können also verwenden, task.GetAwaiter().GetResult()wenn Sie diese Ausbreitungslogik direkt aufrufen möchten.


6
Lösung A in Stephen Clearys Antwort oben verwendet diese Methode. Siehe WaitAndUnwrapException- Quelle.
Orad

Benötigen Sie GetResult (), wenn die von Ihnen aufgerufene Funktion ungültig oder eine Aufgabe ist? Ich meine, wenn Sie keine Ergebnisse zurückbekommen wollen
Batmaci

Ja, andernfalls wird es erst nach Abschluss der Aufgabe blockiert. Alternativ können Sie statt GetAwaiter (). GetResult () .Wait ()
NStuke

1
Das ist der Teil "viele Umstände". Dies hängt vom Gesamt-Threading-Modell ab und davon, was andere Threads tun, um festzustellen, ob das Risiko eines Deadlocks besteht oder nicht.
NStuke

GetAwaiter (). GetResult () kann immer noch Deadlocks verursachen. Es packt die Ausnahme nur in eine vernünftigere aus.
nawfal vor

25

Es gibt jedoch eine gute Lösung, die in jeder Situation funktioniert (fast: siehe Kommentare): eine Ad-hoc-Nachrichtenpumpe (SynchronizationContext).

Der aufrufende Thread wird wie erwartet blockiert, wobei weiterhin sichergestellt wird, dass alle von der asynchronen Funktion aufgerufenen Fortsetzungen nicht blockieren, da sie für den Ad-hoc-SynchronizationContext (Nachrichtenpumpe) gemarshallt werden, der auf dem aufrufenden Thread ausgeführt wird.

Der Code des Ad-hoc-Nachrichtenpumpen-Helfers:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Threading
{
    /// <summary>Provides a pump that supports running asynchronous methods on the current thread.</summary>
    public static class AsyncPump
    {
        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static void Run(Action asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(true);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function
                syncCtx.OperationStarted();
                asyncMethod();
                syncCtx.OperationCompleted();

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static void Run(Func<Task> asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(false);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function and alert the context to when it completes
                var t = asyncMethod();
                if (t == null) throw new InvalidOperationException("No task provided.");
                t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
                t.GetAwaiter().GetResult();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static T Run<T>(Func<Task<T>> asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(false);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function and alert the context to when it completes
                var t = asyncMethod();
                if (t == null) throw new InvalidOperationException("No task provided.");
                t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
                return t.GetAwaiter().GetResult();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Provides a SynchronizationContext that's single-threaded.</summary>
        private sealed class SingleThreadSynchronizationContext : SynchronizationContext
        {
            /// <summary>The queue of work items.</summary>
            private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> m_queue =
                new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
            /// <summary>The processing thread.</summary>
            private readonly Thread m_thread = Thread.CurrentThread;
            /// <summary>The number of outstanding operations.</summary>
            private int m_operationCount = 0;
            /// <summary>Whether to track operations m_operationCount.</summary>
            private readonly bool m_trackOperations;

            /// <summary>Initializes the context.</summary>
            /// <param name="trackOperations">Whether to track operation count.</param>
            internal SingleThreadSynchronizationContext(bool trackOperations)
            {
                m_trackOperations = trackOperations;
            }

            /// <summary>Dispatches an asynchronous message to the synchronization context.</summary>
            /// <param name="d">The System.Threading.SendOrPostCallback delegate to call.</param>
            /// <param name="state">The object passed to the delegate.</param>
            public override void Post(SendOrPostCallback d, object state)
            {
                if (d == null) throw new ArgumentNullException("d");
                m_queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
            }

            /// <summary>Not supported.</summary>
            public override void Send(SendOrPostCallback d, object state)
            {
                throw new NotSupportedException("Synchronously sending is not supported.");
            }

            /// <summary>Runs an loop to process all queued work items.</summary>
            public void RunOnCurrentThread()
            {
                foreach (var workItem in m_queue.GetConsumingEnumerable())
                    workItem.Key(workItem.Value);
            }

            /// <summary>Notifies the context that no more work will arrive.</summary>
            public void Complete() { m_queue.CompleteAdding(); }

            /// <summary>Invoked when an async operation is started.</summary>
            public override void OperationStarted()
            {
                if (m_trackOperations)
                    Interlocked.Increment(ref m_operationCount);
            }

            /// <summary>Invoked when an async operation is completed.</summary>
            public override void OperationCompleted()
            {
                if (m_trackOperations &&
                    Interlocked.Decrement(ref m_operationCount) == 0)
                    Complete();
            }
        }
    }
}

Verwendungszweck:

AsyncPump.Run(() => FooAsync(...));

Eine detailliertere Beschreibung der Async-Pumpe finden Sie hier .



Dies funktioniert in einem Asp.net-Szenario nicht, da Sie HttpContext.Current zufällig verlieren können.
Josh Mouch

12

Für alle, die sich noch mehr mit dieser Frage beschäftigen ...

Wenn Sie hineinschauen, Microsoft.VisualStudio.Services.WebApigibt es eine Klasse namens TaskExtensions. Innerhalb dieser Klasse sehen Sie die statische Erweiterungsmethode Task.SyncResult(), die den Thread wie vollständig blockiert, bis die Aufgabe zurückkehrt.

Intern ruft es auf, task.GetAwaiter().GetResult()was ziemlich einfach ist, aber es ist überladen, an jeder asyncMethode zu arbeiten, die zurückkehrt Task, Task<T>oder Task<HttpResponseMessage>... syntaktischer Zucker, Baby ... Papa hat einen süßen Zahn.

Es sieht so aus, ...GetAwaiter().GetResult()als wäre dies die offizielle Methode von MS, um asynchronen Code in einem blockierenden Kontext auszuführen. Scheint für meinen Anwendungsfall sehr gut zu funktionieren.


3
Du hattest mich bei "wie total nur Blöcke".
Dawood ibn Kareem

9
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);

OpenIdConnectConfiguration config = result.GetAwaiter().GetResult();

Oder benutze dies:

var result=result.GetAwaiter().GetResult().AccessToken

6

Sie können jede asynchrone Methode aus synchronem Code aufrufen, dh bis Sie sie benötigen await. In diesem Fall müssen sie auch markiert asyncwerden.

Wie viele Leute hier vorschlagen, können Sie Wait () oder Result für die resultierende Aufgabe in Ihrer synchronen Methode aufrufen, aber dann erhalten Sie einen blockierenden Aufruf in dieser Methode, der den Zweck der Asynchronisierung zunichte macht.

Wenn Sie Ihre Methode wirklich nicht erstellen können asyncund die synchrone Methode nicht blockieren möchten, müssen Sie eine Rückrufmethode verwenden, indem Sie sie als Parameter an die ContinueWith-Methode für die Aufgabe übergeben.


5
Dann würde das die Methode jetzt nicht synchron aufrufen, oder?
Jeff Mercado

2
Soweit ich weiß, war die Frage, ob Sie eine asynchrone Methode von einer nicht asynchronen Methode aufrufen können. Dies bedeutet nicht, dass die asynchrone Methode blockierend aufgerufen werden muss.
base2

Entschuldigung, Ihr "sie müssen auch markiert asyncwerden" lenkte meine Aufmerksamkeit von dem ab, was Sie wirklich sagten.
Jeff Mercado

Wenn mir die Asynchronität nicht wirklich wichtig ist, ist es in Ordnung, sie so zu nennen (und was ist mit der Möglichkeit von Deadlocks in umschlossenen Ausnahmen, über die Stephen Cleary immer wieder nörgelt?). Ich habe einige Testmethoden (die synchron ausgeführt werden müssen). das testet asynchrone Methoden. Ich muss auf das Ergebnis warten, bevor ich fortfahre, damit ich das Ergebnis der asynchronen Methode testen kann.
Ehrfurcht

6

Ich weiß, dass ich so spät bin. Aber für den Fall, dass jemand wie ich dies auf eine ordentliche, einfache Art und Weise lösen wollte, ohne von einer anderen Bibliothek abhängig zu sein.

Ich habe den folgenden Code von Ryan gefunden

public static class AsyncHelpers
{
    private static readonly TaskFactory taskFactory = new
        TaskFactory(CancellationToken.None,
            TaskCreationOptions.None,
            TaskContinuationOptions.None,
            TaskScheduler.Default);

    /// <summary>
    /// Executes an async Task method which has a void return value synchronously
    /// USAGE: AsyncUtil.RunSync(() => AsyncMethod());
    /// </summary>
    /// <param name="task">Task method to execute</param>
    public static void RunSync(Func<Task> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();

    /// <summary>
    /// Executes an async Task<T> method which has a T return type synchronously
    /// USAGE: T result = AsyncUtil.RunSync(() => AsyncMethod<T>());
    /// </summary>
    /// <typeparam name="TResult">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static TResult RunSync<TResult>(Func<Task<TResult>> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();
}

dann kannst du es so nennen

var t = AsyncUtil.RunSync<T>(() => AsyncMethod<T>());

6
Dies sieht genauso aus wie die obige Antwort,
wenn

2

Nachdem ich stundenlang verschiedene Methoden ausprobiert hatte, mit mehr oder weniger Erfolg, endete ich damit. Es endet nicht in einem Deadlock, während das Ergebnis abgerufen wird, und es wird auch die ursprüngliche Ausnahme und nicht die umschlossene Ausnahme abgerufen und ausgelöst.

private ReturnType RunSync()
{
  var task = Task.Run(async () => await myMethodAsync(agency));
  if (task.IsFaulted && task.Exception != null)
  {
    throw task.Exception;
  }

  return task.Result;
}

1
Funktioniert mit return task.GetAwaiter (). GetResult ();
Per G

Ja, aber was ist mit der ursprünglichen Ausnahme?
Jiří Herník

.Result Ich denke, es ist im Grunde das gleiche wie .GetAwaiter (). GetResult ()
Per G

-2

Es könnte von einem neuen Thread aufgerufen werden (NICHT vom Thread-Pool!):

public static class SomeHelperClass
{ 
       public static T Result<T>(Func<T> func)
        {
            return Task.Factory.StartNew<T>(
                  () => func()
                , TaskCreationOptions.LongRunning
                ).Result;
        }
}
...
content = SomeHelperClass.Result<string>(
  () => response.Content.ReadAsStringAsync().Result
  );

-3

Diese asynchronen Windows-Methoden haben eine raffinierte kleine Methode namens AsTask (). Sie können dies verwenden, damit sich die Methode selbst als Task zurückgibt, sodass Sie Wait () manuell aufrufen können.

In einer Windows Phone 8 Silverlight-Anwendung können Sie beispielsweise Folgendes tun:

private void DeleteSynchronous(string path)
{
    StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
    Task t = localFolder.DeleteAsync(StorageDeleteOption.PermanentDelete).AsTask();
    t.Wait();
}

private void FunctionThatNeedsToBeSynchronous()
{
    // Do some work here
    // ....

    // Delete something in storage synchronously
    DeleteSynchronous("pathGoesHere");

    // Do other work here 
    // .....
}

Hoffe das hilft!


-4

Wenn Sie es ausführen möchten, synchronisieren Sie es

MethodAsync().RunSynchronously()

3
Diese Methode ist zum Starten von Kaltaufgaben vorgesehen. In der Regel geben asynchrone Methoden eine heiße Aufgabe zurück, dh eine Aufgabe, die bereits gestartet wurde. Das Aufrufen RunSynchronously()einer heißen Aufgabe führt zu einem InvalidOperationException. Versuchen Sie es mit diesem Code:Task.Run(() => {}).RunSynchronously();
Theodor Zoulias

-5
   //Example from non UI thread -    
   private void SaveAssetAsDraft()
    {
        SaveAssetDataAsDraft();
    }
    private async Task<bool> SaveAssetDataAsDraft()
    {
       var id = await _assetServiceManager.SavePendingAssetAsDraft();
       return true;   
    }
   //UI Thread - 
   var result = Task.Run(() => SaveAssetDataAsDraft().Result).Result;

2
Deadlock generieren. Löschen Sie besser die Antwort.
PreguntonCojoneroCabrón

Task.Run (() => SaveAssetDataAsDraft ()). Result; - erzeugt keinen Deadlock
Anubis
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.