Frage

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

Ist das überhaupt möglich?

Hier ist ein Beispiel dafür, diese Methoden aus einer asynchronen Methode aufzurufen: http://msdn.microsoft.com/en-us/library/hhh300224(v=vs.110).aspx

Jetzt möchte ich diese asynchronen Methoden aus Synchronisierungsmethoden aufrufen.

War es hilfreich?

Lösung

Die asynchrone Programmierung "wächst" durch die Codebasis. Es ist gewesen Im Vergleich zu einem Zombie -Virus. Die beste Lösung besteht darin, es zu wachsen, aber manchmal ist das nicht möglich.

Ich habe ein paar Typen in meine geschrieben Nito.asyncex Bibliothek für den Umgang mit einer teilweise asynchronen Codebasis. Es gibt jedoch keine Lösung, die in jeder Situation funktioniert.

Lösung a

Wenn Sie über eine einfache asynchrone Methode verfügen, die nicht in seinen Kontext synchronisieren muss, können Sie verwenden Task.WaitAndUnwrapException:

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

Sie machen nicht Möchte benutzen Task.Wait oder Task.Result Weil sie Ausnahmen einpacken in AggregateException.

Diese Lösung ist nur angemessen, wenn MyAsyncMethod synchronisiert nicht in seinen Kontext. Mit anderen Worten, jeder await in MyAsyncMethod sollte enden mit ConfigureAwait(false). Dies bedeutet, dass es keine UI -Elemente aktualisieren oder auf den ASP.NET -Anforderungskontext zugreifen kann.

Lösung b

Wenn MyAsyncMethod Muss sich in seinen Kontext zurücksetzen, dann können Sie möglicherweise verwenden AsyncContext.RunTask einen verschachtelten Kontext bereitstellen:

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

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

var result = AsyncContext.Run(MyAsyncMethod);

(Es ist in Ordnung zu verwenden Task.Result In diesem Beispiel weil RunTask wird sich ausbreiten Task Ausnahmen).

Der Grund, warum Sie möglicherweise brauchen AsyncContext.RunTask Anstatt von Task.WaitAndUnwrapException ist auf eine eher subtile Sackgasse -Möglichkeit auf Winforms/WPF/SL/ASP.NET zurückzuführen:

  1. Eine synchrone Methode ruft eine asynchriese Methode auf, die a erhalten Task.
  2. Die synchrone Methode blockiert auf dem Task.
  3. Das async Methode verwendet await ohne ConfigureAwait.
  4. Das Task kann in dieser Situation nicht fertiggestellt, da es nur dann abgeschlossen ist, wenn die async Methode ist fertig; das async Die Methode kann nicht abgeschlossen sein, da sie versucht, ihre Fortsetzung an die zu planen SynchronizationContext, und WinForms/WPF/SL/ASP.NET erlauben die Fortsetzung nicht aus, da die synchrone Methode in diesem Zusammenhang bereits ausgeführt wird.

Dies ist ein Grund, warum es eine gute Idee ist, sie zu verwenden ConfigureAwait(false) In jedem async Methode so viel wie möglich.

Lösung c

AsyncContext.RunTask Wird in jedem Szenario nicht funktionieren. Zum Beispiel wenn die async Die Methode wartet auf etwas, das ein UI -Ereignis erfordert, und dann werden Sie auch mit dem verschachtelten Kontext tödlich. In diesem Fall könnten Sie das starten async Methode auf dem Threadpool:

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

Diese Lösung erfordert jedoch a MyAsyncMethod Das funktioniert im Thread -Pool -Kontext. 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) zu seinem await Aussagen und verwenden Sie Lösung A.

Update, 2019-05-01: Die aktuellen "am wenigsten begeisterten Praktiken" sind in einem MSDN -Artikel hier.

Andere Tipps

Das Hinzufügen einer Lösung, die mein Problem schließlich löste, spart hoffentlich die Zeit von jemandem.

Lesen Sie zunächst ein paar Artikel von Stephen Cleary:

Aus den "zwei Best Practices" in "Nicht blockieren auf Async Code", hat der erste bei mir nicht funktioniert und der zweite war nicht anwendbar (im Grunde genommen, wenn ich verwenden kann await, Ich tue!).

Also hier ist meine Problemumgehung: Wickeln Sie den Anruf in a Task.Run<>(async () => await FunctionAsync()); Und hoffentlich nein Sackgasse 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;
    }
}

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

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 Basisklassen von Microsoft.aspnet.identity haben nur asynchronische Methoden und um sie als Synchronisierung zu bezeichnen, gibt es Klassen mit Erweiterungsmethoden, die wie (Beispielsutzung) aussehen:

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 über die Lizenzbedingungen von Code besorgt haben, finden Sie hier einen Link zu sehr ähnlichem Code (fügt nur Unterstützung für Kultur im Thread hinzu), die Kommentare enthält, um anzuzeigen, dass es von Microsoft lizenziert wird. https://github.com/aspnet/aspnetidentity/blob/master/src/microsoft.aspnet.ididentity.core/asynchelper.cs

Async Main ist jetzt Teil von C# 7.2 und kann in den Projekteinstellungen für erweiterte Projekte aktiviert werden.

Für C# <7.2 ist 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-topics-subscriptions

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 "Warten" als "Starten Sie diese lang laufende Aufgabe und geben dann die Steuerung an die aufrufende Methode zurück". Sobald die langjährige Aufgabe erledigt ist, wird der Code danach ausgeführt. Der Code nach dem Warten ähnelt dem, was früher Rückrufmethoden waren. Der große Unterschied ist, dass der logische Fluss nicht unterbrochen wird, was das Schreiben und Lesen viel einfacher erleichtert.

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

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

Die am meisten akzeptierte Antwort ist nicht ganz richtig. Es gibt eine Lösung, die in jeder Situation funktioniert: eine Ad-hoc-Nachrichtenpumpe (SynchronisationContext).

Der aufrufende Thread wird wie erwartet blockiert und stellt dennoch sicher, dass alle von der asynchronisierten Funktion genannten Kontinuationen nicht abgestimmt sind, da sie in die Ad-hoc-Synchronisationcontext (Message Pump) auf dem auf dem Anruf thread ausgeführten Ad-hoc-Synchronisierungscontext (Message Pump) gesorgt werden.

Der Code des Ad-hoc-Meldungspumpe-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 asynchronen Pumpe ist verfügbar hier.

Für alle, die auf diese Frage nicht aufmerksam machen ...

Wenn Sie hineinschauen Microsoft.VisualStudio.Services.WebApi Es gibt eine Klasse namens TaskExtensions. Innerhalb dieser Klasse sehen Sie die statische Erweiterungsmethode Task.SyncResult(), was wie völlig nur den Thread blockiert, bis die Aufgabe zurückkehrt.

Intern ruft es an task.GetAwaiter().GetResult() Das ist ziemlich einfach, aber es ist überlastet, um an jedem zu arbeiten async Methode, die zurückkehren Task, Task<T> oder Task<HttpResponseMessage>... Syntaktischer Zucker, Baby ... Daddy hat einen süßen Zahn.

Es sieht aus wie ...GetAwaiter().GetResult() ist die MS-Official-Art, asynchronen Code in einem Blockierungskontext auszuführen. Scheint für meinen Anwendungsfall sehr gut zu funktionieren.

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

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

Oder verwenden Sie dies:

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

Sie können jede asynchrone Methode aus synchronem Code aufrufen, dh bis Sie müssen await In diesem Fall müssen sie markiert werden async zu.

Wie viele Leute hier vorschlagen, können Sie Wait () oder das Ergebnis der resultierenden Aufgabe in Ihrer synchronen Methode aufrufen, aber dann haben Sie einen Blockierungsanruf in dieser Methode, die den Zweck von Async besiegt.

Ich kann deine Methode wirklich nicht machen async Und Sie möchten die synchrone Methode nicht sperren, dann müssen Sie eine Rückrufmethode verwenden, indem Sie sie als Parameter an die fortlaufende Methode für die Aufgabe übergeben.

Ich weiß, dass ich so spät bin. Aber für den Fall, dass jemand wie ich dies auf eine ordentliche, einfache Weise und ohne eine andere Bibliothek lösen wollte.

Ich fand Folgendes Code aus Ryan

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 können Sie es so nennen

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

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

MethodAsync().RunSynchronously()

Diese Windows -asynchronisierten Methoden haben eine raffinierte kleine Methode namens Astask (). Sie können diese verwenden, um die Methode als Aufgabe zurückzugeben, damit Sie Warte () darauf aufrufen können.

Beispielsweise können Sie auf einer Windows Phone 8 Silverlight -Anwendung Folgendes ausführen:

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!

   //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;
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top