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

861

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

Czy to w ogóle możliwe?

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

Teraz zastanawiam się nad wywołaniem tych metod asynchronicznych z metod synchronizacji.

Wieża
źródło
2
Na to też wpadłem. Przesłaniając RoleProvider, nie możesz zmienić podpisu metody GetRolesForUser, więc nie możesz ustawić metody asynchronicznej, więc nie możesz użyć funkcji wyczekania, aby wywoływać api asynchronicznie. Moim tymczasowym rozwiązaniem było dodanie metod synchronicznych do mojej ogólnej klasy HttpClient, ale chciałbym wiedzieć, czy jest to możliwe (i jakie mogą być tego konsekwencje).
Timothy Lee Russell,
1
Ponieważ twoja async void Foo()metoda nie zwraca a Taskoznacza to, że osoba dzwoniąca nie może wiedzieć, kiedy się zakończy, musi Taskzamiast tego powrócić .
Dai
1
Łączenie pokrewnych pytań / odpowiedzi na temat tego, jak to zrobić w wątku interfejsu użytkownika.
noseratio

Odpowiedzi:

710

Programowanie asynchroniczne „rośnie” poprzez bazę kodu. Został porównany do wirusa zombie . Najlepszym rozwiązaniem jest umożliwienie jej wzrostu, ale czasami jest to niemożliwe.

Napisałem kilka typów w mojej bibliotece Nito.AsyncEx do radzenia sobie z częściowo asynchroniczną bazą kodu. Jednak nie ma rozwiązania, które działałoby w każdej sytuacji.

Rozwiązanie A

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

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

Zdajesz nie chcą używać Task.Waitalbo Task.Resultponieważ owinąć wyjątki AggregateException.

To rozwiązanie jest odpowiednie tylko wtedy, MyAsyncMethodgdy nie synchronizuje się z kontekstem. Innymi słowy, każdy awaitw MyAsyncMethodpowinny kończyć ConfigureAwait(false). Oznacza to, że nie może zaktualizować żadnych elementów interfejsu użytkownika ani uzyskać dostępu do kontekstu żądania ASP.NET.

Rozwiązanie B

Jeśli MyAsyncMethodkonieczne jest zsynchronizowanie z powrotem do kontekstu, możesz użyć opcji, AsyncContext.RunTaskaby zapewnić zagnieżdżony kontekst:

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

* Aktualizacja 14.04.2014: W nowszych wersjach biblioteki interfejs API wygląda następująco:

var result = AsyncContext.Run(MyAsyncMethod);

(W Task.Resulttym przykładzie można używać, ponieważ RunTaskbędą propagować Taskwyjątki).

AsyncContext.RunTaskZamiast tego możesz potrzebować Task.WaitAndUnwrapExceptionraczej subtelnej impasu, która zdarza się w WinForms / WPF / SL / ASP.NET:

  1. Metoda synchroniczna wywołuje metodę asynchroniczną, uzyskując Task.
  2. Metoda synchroniczna blokuje oczekiwanie na Task.
  3. asyncMetoda wykorzystuje awaitbez ConfigureAwait.
  4. Nie Taskmożna ukończyć w tej sytuacji, ponieważ kończy się dopiero po zakończeniu asyncmetody; asyncmetoda może nie jest kompletna, ponieważ stara się zaplanować swoją kontynuację do SynchronizationContexti WinForms / WPF / SL / ASP.NET nie pozwoli na kontynuację uruchomić ponieważ metoda synchroniczna jest już uruchomiony w tym kontekście.

Jest to jeden z powodów, dla których warto stosować ConfigureAwait(false)każdą asyncmetodę w jak największym stopniu.

Rozwiązanie C

AsyncContext.RunTasknie będzie działać w każdym scenariuszu. Na przykład, jeśli asyncmetoda czeka na coś, co wymaga zdarzenia interfejsu użytkownika do ukończenia, zakleszczysz się nawet w zagnieżdżonym kontekście. W takim przypadku możesz uruchomić asyncmetodę w puli wątków:

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

To rozwiązanie wymaga jednak MyAsyncMethoddziałania w kontekście puli wątków. Nie może więc aktualizować elementów interfejsu użytkownika ani uzyskiwać dostępu do kontekstu żądań ASP.NET. W takim przypadku możesz również dodać ConfigureAwait(false)do jego awaitoświadczeń i użyć rozwiązania A.

Aktualizacja, 01.05.2019: Obecne „najgorsze praktyki” znajdują się w artykule MSDN tutaj .

Stephen Cleary
źródło
9
Rozwiązanie A wydaje się być tym, czego chcę, ale wygląda na to, że task.WaitAndUnwrapException () nie dostał się do .Net 4.5 RC; ma tylko task.Wait (). Masz pomysł, jak to zrobić w nowej wersji? Czy jest to niestandardowa metoda rozszerzenia, którą napisałeś?
deadlydog
3
WaitAndUnwrapExceptionto moja własna metoda z mojej biblioteki AsyncEx . Oficjalne biblioteki .NET nie zapewniają dużej pomocy w mieszaniu kodu synchronizacji i asynchronicznego (i ogólnie nie powinieneś tego robić!). Czekam na .NET 4.5 RTW i nowego laptopa bez XP, zanim zaktualizuję AsyncEx do działania w wersji 4.5 (nie mogę obecnie programować w wersji 4.5, ponieważ utknąłem na XP przez kilka tygodni).
Stephen Cleary,
12
AsyncContextma teraz Runmetodę, która przyjmuje wyrażenie lambda, więc powinieneś użyćvar result = AsyncContext.Run(() => MyAsyncMethod());
Stephen Cleary
1
Mam twoją bibliotekę poza Nugetem, ale tak naprawdę nie ma RunTaskmetody. Najbliższa rzecz, jaką mogłem znaleźć Run, to nie ma Resultwłasności.
Asad Saeeduddin
3
@Asad: Tak, ponad 2 lata później interfejs API zmienił się. Teraz możesz po prostu powiedziećvar result = AsyncContext.Run(MyAsyncMethod);
Stephen Cleary
313

Dodając rozwiązanie, które w końcu rozwiązało mój problem, mam nadzieję, że zaoszczędzi komuś czas.

Najpierw przeczytaj kilka artykułów Stephena Cleary'ego :

Z „dwóch najlepszych praktyk” w „Don't Block on Async Code”, pierwsza nie działała dla mnie, a druga nie miała zastosowania (w zasadzie jeśli mogę await, to mogę!).

Oto moje obejście: zamknij połączenie w środku Task.Run<>(async () => await FunctionAsync());i mam 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;
    }
}
Tohid
źródło
5
Dwa lata później jestem ciekawy, jak to rozwiązanie się utrzymuje. Jakieś wieści? Czy subtelność tego podejścia jest utracona dla początkujących?
Dan Esparza
26
To nie jest impas, prawda, ale po prostu dlatego, że jest zmuszony do uruchomienia w nowym wątku, poza kontekstem synchronizacji wątku inicjującego. Istnieją jednak środowiska, w których jest to bardzo odradzane: w szczególności aplikacje internetowe. Może to skutecznie zmniejszyć o połowę dostępne wątki dla serwera WWW (jeden wątek dla żądania i jeden dla tego). Im więcej to robisz, tym gorzej. Może to doprowadzić do zakleszczenia całego serwera WWW.
Chris Pratt,
29
@ChrisPratt - Być może masz rację, ponieważ Task.Run()nie jest to najlepsza praktyka w kodzie asynchronicznym. Ale znowu, jaka jest odpowiedź na pierwotne pytanie? Nigdy nie wywoływać metody asynchronicznej synchronicznie? Chcemy, ale w prawdziwym świecie czasem musimy.
Tohid
1
@Tohid możesz wypróbować bibliotekę Stephena Cleary'ego. Widziałem, jak ludzie to zakładają, a Parallel.ForEachnadużycia nie będą miały wpływu na „rzeczywisty świat” i ostatecznie spowodują uszkodzenie serwerów. Ten kod jest odpowiedni dla aplikacji konsolowych, ale jak mówi @ChrisPratt, nie należy go używać w aplikacjach internetowych. Może działać „teraz”, ale nie jest skalowalny.
makhdumi
1
Jestem zaintrygowany, aby rozpocząć tworzenie nowych kont SO, odpowiadając na pytania, aby zdobyć wystarczającą liczbę punktów, aby głosować na to ...
Giannis Paraskevopoulos
206

Microsoft zbudował klasę wewnętrzną AsyncHelper, aby uruchomić Async jako synchronizację. Ź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();
    }
}

Klasy podstawowe Microsoft.AspNet.Identity mają tylko metody Async, a do wywołania ich jako Synchronizacja istnieją klasy z metodami rozszerzenia, które wyglądają (przykładowe użycie):

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 osób zainteresowanych warunkami licencyjnymi kodu, tutaj znajduje się link do bardzo podobnego kodu (dodaje tylko obsługę kultury w wątku), który zawiera komentarze wskazujące, że jest to licencja MIT firmy Microsoft. https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs

Erik Philips
źródło
2
Moje metody asynchroniczne oczekują na inne metody asynchroniczne. NIE dekoruję żadnymi z moich awaitpołączeń ConfigureAwait(false). Próbowałem AsyncHelper.RunSyncwywołać funkcję asynchroniczną z Application_Start()funkcji w Global.asax i wydaje się, że działa. Czy to oznacza, że AsyncHelper.RunSyncniezawodnie nie jest podatny na problem z impasem „marszałka z powrotem w kontekście dzwoniącego”, o którym czytałem w innym miejscu w tym poście?
Bob.at.Indigo.Health
1
@ Bob.at.SBS zależy od tego, co robi kod. To nie jest tak proste , jakbym korzystał z tego kodu, czy jestem bezpieczny . Jest to bardzo minimalny i półbezpieczny sposób synchronicznego uruchamiania poleceń asynchronicznych, można go łatwo użyć niewłaściwie, aby spowodować zakleszczenia.
Erik Philips
1
Dzięki. 2 dalsze pytania: 1) Czy możesz podać przykład czegoś, czego metoda asynchroniczna chce uniknąć, co spowodowałoby zakleszczenie, oraz 2) czy zakleszczenia w tym kontekście często zależą od czasu? Jeśli to działa w praktyce, czy nadal mogę mieć zakleszczenie zależne od czasu czające się w moim kodzie?
Bob.at.Indigo.Health
@ Bob.at.SBS Polecam zadawanie pytań za pomocą przycisku Zadaj pytanie w prawym górnym rogu. Możesz dołączyć link do tego pytania lub odpowiedzi w swoim pytaniu jako odniesienie.
Erik Philips
1
@ Bob.at ... kod podany przez Erika działa idealnie pod Asp. net mvc5 i EF6, ale nie, gdy wypróbowałem inne rozwiązanie (ConfigureAwait (false) .GetAwaiter (). GetResult () lub .result), które całkowicie zawiesza moją aplikację internetową
LeonardoX
150

async Main jest teraz częścią C # 7.2 i można go włączyć w zaawansowanych ustawieniach kompilacji projektu.

Dla C # <7.2 poprawny sposób to:

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


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

Zostanie to wykorzystane w wielu dokumentacjach Microsoft, na przykład: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use- subskrypcje tematów

Lee Smith
źródło
11
Nie mam pojęcia DLACZEGO ktoś to zagłosował. To zadziałało dla mnie świetnie. Bez tej poprawki musiałbym propagować JAKIEKOLWIEK WSZYSTKO.
Więzień ZERO
11
Dlaczego to jest lepsze niż MainAsync().Wait()?
zmiażdżyć
8
Zgadzam się. Potrzebujesz tylko MainAsync (). Wait () zamiast tego wszystkiego.
Hajjat
8
@crush Opisałem, w jaki sposób można uniknąć niektórych zakleszczeń. W niektórych sytuacjach wywołanie funkcji .Wait () z interfejsu użytkownika lub wątku asp.net powoduje zakleszczenie. zakleszczenia asynchroniczne
David
6
@ClintB: Absolutnie nie powinieneś tego robić w programie ASP.NET Core. Aplikacje internetowe są szczególnie narażone na głód wątków i za każdym razem, gdy to robisz, wyciągasz wątek z puli, który w innym przypadku byłby wykorzystywany do obsługi żądania. Jest to mniej problematyczne dla aplikacji stacjonarnych / mobilnych, ponieważ tradycyjnie są one przeznaczone dla jednego użytkownika.
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

}

Odczytujesz słowo kluczowe „czekaj” jako „rozpocznij to długo działające zadanie, a następnie przywróć kontrolę nad metodą wywoływania”. Po zakończeniu długotrwałego zadania wykonuje on kod po nim. Kod po oczekiwaniu jest podobny do tego, co kiedyś było metodami CallBack. Duża różnica polega na tym, że przepływ logiczny nie jest przerywany, co znacznie ułatwia pisanie i czytanie.

Despertar
źródło
15
Waitomija wyjątki i ma możliwość impasu.
Stephen Cleary
Myślałem, że jeśli wywołasz metodę asynchroniczną bez użycia await, zostanie ona wykonana synchronicznie. Przynajmniej to działa dla mnie (bez dzwonienia myTask.Wait). Właściwie dostałem wyjątek, gdy próbowałem zadzwonić, myTask.RunSynchronously()ponieważ został już wykonany!
awe
2
Podoba mi się ta odpowiedź. Dobre komentarze do edycji, małe i eleganckie. Dziękujemy za pomoc! Wciąż uczę się współbieżności, więc wszystko pomaga :)
kayleeFrye_onDeck
2
Czy ta odpowiedź powinna nadal działać na dzień dzisiejszy? Właśnie wypróbowałem to w projekcie MVC Razor, a aplikacja po prostu zawiesza się na dostęp .Result.
Gone Coding
7
@TrueBlueAussie To zakleszczenie kontekstu synchronizacji. Twój kod asynchroniczny wraca do kontekstu synchronizacji, ale jest on wówczas blokowany przez Resultwywołanie, więc nigdy go nie dociera. I Resultnigdy się nie kończy, ponieważ czeka na kogoś, kto czeka na Resultkoniec, w zasadzie: D
Luaan
40

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

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

NStuke
źródło
6
Rozwiązanie A w powyższej odpowiedzi Stephena Cleary'ego wykorzystuje tę metodę. Zobacz źródło WaitAndUnwrapException .
orad
czy potrzebujesz użyć GetResult (), jeśli wywoływana funkcja jest nieważna lub zadana? Mam na myśli, jeśli nie chcesz odzyskać żadnych wyników
batmaci,
Tak, w przeciwnym razie nie zostanie zablokowany do czasu zakończenia zadania. Alternatywnie zamiast wywoływać funkcję GetAwaiter (). GetResult () można wywołać funkcję .Wait ()
NStuke
1
To część „wielu okoliczności”. To zależy od ogólnego modelu wątków i tego, co robią inne wątki, aby ustalić, czy istnieje ryzyko zakleszczenia, czy nie.
NStuke
24

Istnieje jednak dobre rozwiązanie, które działa w (prawie: patrz komentarze) 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ływane z funkcji asynchronicznej nie zostaną zakleszczone, ponieważ zostaną skierowane do ad-hoc SynchronizationContext (pompy komunikatów) działającej w wątku wywołującym.

Kod pomocnika pompy komunikatów ad-hoc:

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

Stosowanie:

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

Bardziej szczegółowy opis pompy asynchronicznej znajduje się tutaj .

Robert J.
źródło
Kontekst wyjątku i AsyncPump stackoverflow.com/questions/23161693/…
PreguntonCojoneroCabrón
Nie działa to w scenariuszu Asp.net, ponieważ można losowo utracić HttpContext.Current.
Josh Mouch
12

Do każdego, kto zwraca uwagę na to pytanie ...

Jeśli zajrzysz Microsoft.VisualStudio.Services.WebApido środka, istnieje klasa o nazwie TaskExtensions. W tej klasie zobaczysz metodę statycznego rozszerzenia Task.SyncResult(), która jak całkowicie blokuje wątek, dopóki zadanie nie powróci.

Wewnętrznie wywołuje task.GetAwaiter().GetResult()to dość proste, ale przeciążone jest pracą nad każdą asyncmetodą, która zwraca Task, Task<T>lub Task<HttpResponseMessage>... cukrem syntaktycznym, kochanie ... tata ma słodycze.

Wygląda na ...GetAwaiter().GetResult()to, że jest to oficjalny sposób MS na wykonanie kodu asynchronicznego w kontekście blokującym. Wydaje się, że działa bardzo dobrze w moim przypadku użycia.

jrypkahauer
źródło
3
Miałeś mnie w „jak totalnie po prostu bloki”.
Dawood ibn Kareem
9
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
rajesh A
źródło
6

Możesz wywołać dowolną metodę asynchroniczną z kodu synchronicznego, to znaczy, dopóki nie będziesz musiał awaitna nich użyć, w takim przypadku muszą one również zostać oznaczone async.

Jak sugeruje tutaj wiele osób, możesz wywołać funkcję Wait () lub Wynik w wynikowym zadaniu w metodzie synchronicznej, ale w rezultacie w tej metodzie kończy się wywołanie blokujące, które w pewnym sensie nie spełnia celu asynchronizacji.

Naprawdę nie możesz stworzyć metody asynci nie chcesz blokować metody synchronicznej, wtedy będziesz musiał użyć metody wywołania zwrotnego, przekazując ją jako parametr do metody ContinueWith przy zadaniu.

base2
źródło
5
Czy to nie byłoby teraz wywoływanie metody synchronicznie, prawda?
Jeff Mercado
2
Jak rozumiem, pytanie brzmiało: czy można wywołać metodę asynchroniczną z metody innej niż asynchroniczna. Nie oznacza to konieczności wywoływania metody asynchronicznej w sposób blokujący.
base2
Przepraszam, twoje „one też muszą być oznaczone async” odwróciły moją uwagę od tego, co naprawdę mówiłeś.
Jeff Mercado
Jeśli tak naprawdę nie dbam o asynchroniczność, to czy można to tak nazwać (a co z możliwością zakleszczenia w opakowanych wyjątkach, o których Stephen Cleary wciąż dręczy?) Mam kilka metod testowych (które muszą być wykonywane synchronicznie) który testuje metody asynchroniczne. Muszę poczekać na wynik przed kontynuowaniem, aby móc przetestować wynik metody asynchronicznej.
awe
6

Wiem, że jestem spóźniony. Ale na wypadek, gdyby ktoś taki jak ja chciał rozwiązać to w prosty, schludny sposób i bez polegania na innej bibliotece.

Znalazłem następujący fragment kodu od Ryana

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

możesz to tak nazwać

var t = AsyncUtil.RunSync<T>(() => AsyncMethod<T>());
Wahid Bitar
źródło
6
Wygląda to dokładnie tak jak powyższa odpowiedź : czy coś mi umknęło
inlokesh 24.04.19
2

Po godzinach próbowania różnych metod, z mniejszym lub większym sukcesem, na tym się skończyłem. Nie kończy się impasem podczas uzyskiwania wyniku, a także pobiera i generuje wyjątek oryginalny, a nie zapakowany.

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

  return task.Result;
}
Jiří Herník
źródło
Działa z funkcją return.GetAwaiter (). GetResult ();
Per G
tak, ale co z oryginalnym wyjątkiem?
Jiří Herník
.Result myślę, że jest to w zasadzie to samo, co .GetAwaiter (). GetResult ()
Per G
-2

Można go wywołać z nowego wątku (NIE z puli wątków!):

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
  );
Garm
źródło
-3

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

Na przykład w aplikacji Silverlight systemu Windows Phone 8 możesz 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!

Lisi
źródło
-4

Jeśli chcesz go uruchomić, zsynchronizuj

MethodAsync().RunSynchronously()
smj
źródło
3
Ta metoda jest przeznaczona do uruchamiania zimnych zadań. Zazwyczaj metody asynchroniczne zwracają gorące zadanie, innymi słowy zadanie, które już się rozpoczęło. wywołanie RunSynchronously()gorącego zadania do InvalidOperationException. Wypróbuj z tym kodem: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;
Arvind Kumar Chaodhary
źródło
2
Wygeneruj impas. Lepiej usuń odpowiedź.
PreguntonCojoneroCabrón
Task.Run (() => SaveAssetDataAsDraft ()). Wynik; - nie generuje impasu
Anubis