Jak wywołać metodę asynchroniczną z metody synchronicznej w C#?

Mam metodę public async void Foo(), którą chcę wywołać z metody synchronicznej. Do tej pory wszystko, co widziałem z dokumentacji MSDN, to wywoływanie metod async za pomocą metod async, ale cały mój program nie jest zbudowany z metod async.

Czy to w ogóle możliwe?

Oto przykład wywołania tych metod z metody asynchronicznej: http://msdn.microsoft.com/en-us/library/hh300224 (v=vs.110). aspx

Teraz patrzę na wywołanie tych metod asynchronicznych z metod synchronizacji.

Author: BoltClock, 2012-02-18

11 answers

Programowanie asynchroniczne "rośnie" poprzez bazę kodu. Został[37]}porównany do wirusa zombie [38]}. Najlepszym rozwiązaniem jest umożliwienie jej wzrostu, ale czasami nie jest to możliwe.

Napisałem kilka typów w moim Nito.Biblioteka AsyncEx do obsługi częściowo asynchronicznej bazy kodu. Nie ma jednak rozwiązania, które sprawdzi się w każdej sytuacji.

Rozwiązanie A

Jeśli masz prostą metodę asynchroniczną, która nie musi Synchronizuj z powrotem do kontekstu, możesz użyć Task.WaitAndUnwrapException:

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

Robisz nie chcesz używać lub Task.Result ponieważ zawierają wyjątki w AggregateException.

To rozwiązanie jest właściwe tylko wtedy, gdy MyAsyncMethod nie zsynchronizuje się z kontekstem. Innymi słowy, każda await w MyAsyncMethod powinna kończyć się ConfigureAwait(false). Oznacza to, że nie może zaktualizować żadnych elementów interfejsu ani uzyskać dostępu do ASP.NET kontekst żądania.

Rozwiązanie B

If MyAsyncMethod does need to synchronize back to jego kontekst może być w stanie użyć AsyncContext.RunTask, aby zapewnić zagnieżdżony kontekst:

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

*Update 4/14/2014: w nowszych wersjach biblioteki API jest następujące:

var result = AsyncContext.Run(MyAsyncMethod);

(W tym przykładzie można użyć Task.Result, ponieważ {[15] } będzie propagować wyjątki Task).

Powodem, dla którego możesz potrzebować AsyncContext.RunTask zamiast Task.WaitAndUnwrapException, jest raczej subtelna możliwość impasu, która ma miejsce na WinForms/WPF / SL / ASP.NET: {]}

  1. metoda synchroniczna wywołuje metodę asynchroniczną, uzyskując Task.
  2. metoda synchroniczna blokuje oczekiwanie na Task.
  3. metoda async wykorzystuje await bez ConfigureAwait.
  4. Task nie może zakończyć się w tej sytuacji, ponieważ kończy się dopiero po zakończeniu metody async; metoda async nie może zakończyć się, ponieważ próbuje zaplanować kontynuację do SynchronizationContext, A WinForms/WPF/SL / ASP. NET nie pozwoli na kontynuację, ponieważ metoda synchroniczna jest już działa w tym kontekście.
Jest to jeden z powodów, dla których warto używać ConfigureAwait(false) w każdej metodzie async w jak największym stopniu.

Rozwiązanie C

AsyncContext.RunTask nie zadziała w każdym scenariuszu. Na przykład, jeśli metoda async oczekuje czegoś, co wymaga zakończenia zdarzenia UI, to zablokujesz nawet zagnieżdżony kontekst. W takim przypadku można uruchomić metodę async na puli wątków:

var task = TaskEx.RunEx(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();

Jednak to rozwiązanie wymaga MyAsyncMethod, która będzie działać w kontekście puli wątków. Więc nie może zaktualizować elementów interfejsu ani uzyskać dostępu do ASP.NET kontekst żądania. I w takim przypadku możesz równie dobrze dodać ConfigureAwait(false) do jego await wypowiedzi i użyć rozwiązania A.

 448
Author: Stephen Cleary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-07 13:51:55

Microsoft zbudował klasę AsyncHelper (wewnętrzną) do uruchamiania async jako synchronizacji. Źródło wygląda następująco:

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();
    }
}
Microsoft.AspNet.Klasy bazowe tożsamości mają tylko metody asynchroniczne i w celu wywołania ich jako Sync istnieją klasy z metodami rozszerzeń, które wyglądają tak (przykład użycia):
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));
}

Dla zainteresowanych warunkami licencji kodu, oto link do bardzo podobnego kodu (tylko dodaje wsparcie dla kultury w wątku), który ma komentarze wskazujące, że jest MIT Licencjonowane przez Microsoft. https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs

 145
Author: Erik Philips,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-07 15:32:38

Dodanie rozwiązania, które w końcu rozwiązało mój problem, mam nadzieję, że zaoszczędzi czyjś czas.

Najpierw przeczytaj kilka artykułów Stephen Cleary :

Z "dwóch najlepszych praktyk" w "nie blokuj kodu asynchronicznego", pierwsza nie działała dla mnie ,a druga nie miała zastosowania (w zasadzie jeśli mogę użyć await, to tak!).

Oto moje obejście: w środku Task.Run<>(async () => await FunctionAsync()); i miejmy nadzieję, że nie będzie już impasu.

Oto Mój kod:

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;
    }
}
 137
Author: Tohid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-29 20:51:56

Async Main jest teraz częścią C # 7.2 i może być włączony w zaawansowanych ustawieniach budowania projektów.

Dla C#

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


static async Task MainAsync()
{
   /*await stuff here*/
}
 67
Author: Lee Smith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-22 13:36:02
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

}

Odczytujesz słowo kluczowe 'wait' jako "uruchom to długo uruchomione zadanie, a następnie zwróć kontrolę do metody wywołującej". Po wykonaniu długotrwałego zadania wykonuje kod po nim. Kod po oczekiwaniu jest podobny do tego, co kiedyś były metody wywołania zwrotnego. Duża różnica polega na tym, że logiczny przepływ nie jest przerywany, co znacznie ułatwia pisanie i czytanie.

 42
Author: Despertar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-20 17:59:58

Nie jestem w 100% pewien, ale uważam, że technika opisana w Ten blog powinna działać w wielu okolicznościach:

Możesz więc użyć task.GetAwaiter().GetResult(), jeśli chcesz bezpośrednio wywołać tę logikę propagacji.

 26
Author: NStuke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-11 01:58:05

Najbardziej akceptowana odpowiedź nie jest do końca poprawna. Istnieje rozwiązanie, które sprawdza się w każdej sytuacji: pompa komunikatów ad-hoc (SynchronizationContext).

Wątek wywołujący zostanie zablokowany zgodnie z oczekiwaniami, jednocześnie zapewniając, że wszystkie kontynuacje wywołane z funkcji async nie zablokują się, ponieważ zostaną przekierowane do Ad-hoc SynchronizationContext (pompa wiadomości) uruchomiona w wątku wywołującym.

Kod pompy wiadomości ad-hoc "helper": {]}

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

Użycie:

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

Bardziej szczegółowy opis pompy asynchronicznej jest dostępny tutaj .

 18
Author: Robert J,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-02 11:09:26

Możesz wywołać dowolną metodę asynchroniczną z kodu synchronicznego, czyli dopóki nie będziesz musiał await Na nich, w którym to przypadku również muszą być oznaczone async.

Jak Wiele osób sugeruje tutaj, możesz wywołać Wait() lub Result na wynikowym zadaniu w Twojej synchronicznej metodzie, ale potem skończysz z blokującym wywołaniem w tej metodzie, co w pewnym sensie niszczy cel asynchronicznego działania.

Naprawdę nie możesz stworzyć swojej metody async i nie chcesz zamykać metody synchronicznej, następnie będziesz musiał użyć metody wywołania zwrotnego, przekazując ją jako parametr do metody ContinueWith na zadaniu.

 3
Author: base2,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-18 18:11:16
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);

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

Lub użyj tego:

var result=result.GetAwaiter().GetResult().AccessToken
 2
Author: rajesh A,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-21 13:09:23
   //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;
 -3
Author: Arvind Kumar Chaodhary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-29 13:17:50

Te metody asynchroniczne windows mają sprytną małą metodę o nazwie AsTask(). Możesz tego użyć, aby metoda zwróciła się jako zadanie, aby można było ręcznie wywołać metodę Wait ().

Na przykład w aplikacji Windows Phone 8 Silverlight można wykonać następujące czynności:

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 
    // .....
}
Mam nadzieję, że to pomoże!
 -3
Author: Foxy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-12 21:59:53