Jak uruchomić synchronicznie metodę zadania asynchronicznego <T>?

628

Uczę się o asynchronizacji / czekaniu i wpadłem na sytuację, w której muszę synchronicznie wywołać metodę asynchroniczną. Jak mogę to zrobić?

Metoda asynchroniczna:

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

Normalne użycie:

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

Próbowałem użyć następujących opcji:

Task<Customer> task = GetCustomers();
task.Wait()

Task<Customer> task = GetCustomers();
task.RunSynchronously();

Task<Customer> task = GetCustomers();
while(task.Status != TaskStatus.RanToCompletion)

Próbowałem też stąd sugestii , jednak nie działa ona, gdy dyspozytor jest w stanie zawieszenia.

public static void WaitWithPumping(this Task task) 
{
        if (task == null) throw new ArgumentNullException(“task”);
        var nestedFrame = new DispatcherFrame();
        task.ContinueWith(_ => nestedFrame.Continue = false);
        Dispatcher.PushFrame(nestedFrame);
        task.Wait();
}

Oto wyjątek i ślad stosu z wywołania RunSynchronously:

System.InvalidOperationException

Wiadomość : RunSynchronously nie można wywołać w przypadku zadania niezwiązanego z delegatem.

InnerException : null

Źródło : mscorlib

StackTrace :

          at System.Threading.Tasks.Task.InternalRunSynchronously(TaskScheduler scheduler)
   at System.Threading.Tasks.Task.RunSynchronously()
   at MyApplication.CustomControls.Controls.MyCustomControl.CreateAvailablePanelList() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 638
   at MyApplication.CustomControls.Controls.MyCustomControl.get_AvailablePanels() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 233
   at MyApplication.CustomControls.Controls.MyCustomControl.<CreateOpenPanelList>b__36(DesktopPanel panel) in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 597
   at System.Collections.Generic.List`1.ForEach(Action`1 action)
   at MyApplication.CustomControls.Controls.MyCustomControl.<CreateOpenPanelList>d__3b.MoveNext() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 625
   at System.Runtime.CompilerServices.TaskAwaiter.<>c__DisplayClass7.<TrySetContinuationForAwait>b__1(Object state)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.DispatcherOperation.InvokeImpl()
   at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
   at System.Threading.ExecutionContext.runTryCode(Object userData)
   at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Windows.Threading.DispatcherOperation.Invoke()
   at System.Windows.Threading.Dispatcher.ProcessQueue()
   at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.Dispatcher.InvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
   at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
   at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
   at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.Run()
   at System.Windows.Application.RunDispatcher(Object ignore)
   at System.Windows.Application.RunInternal(Window window)
   at System.Windows.Application.Run(Window window)
   at System.Windows.Application.Run()
   at MyApplication.App.Main() in C:\Documents and Settings\...\MyApplication\obj\Debug\App.g.cs:line 50
   at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
   at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()
Rachel
źródło
46
Najlepszą odpowiedzią na pytanie „Jak mogę wywołać metodę asynchroniczną synchronicznie” jest „nie”. Są hacki, które próbują zmusić go do działania, ale wszystkie mają bardzo subtelne pułapki. Zamiast tego wykonaj kopię zapasową i napraw kod, który powoduje, że musisz to zrobić.
Stephen Cleary
57
@Stephen Cleary Absolutnie się zgadzam, ale czasami jest to po prostu nieuniknione, na przykład gdy twój kod jest zależny od interfejsu API innej firmy, który nie używa asynchronizacji / oczekiwania. Ponadto, jeśli wiąże się z właściwościami WPF podczas korzystania z MVVM, dosłownie niemożliwe jest użycie asynchronizacji / oczekiwania, ponieważ nie jest to obsługiwane we właściwościach.
Contango,
3
@StephenCleary Nie zawsze. Buduję bibliotekę DLL, która zostanie zaimportowana do GeneXus . Nie obsługuje słów kluczowych async / oczekuj, więc muszę używać tylko metod synchronicznych.
Dinei
5
@StephenCleary 1) GeneXus jest narzędziem trzeciego punktu i nie mam dostępu do jego kodu źródłowego; 2) GeneXus nie ma nawet implementacji „funkcji”, więc nie mogę zrozumieć, w jaki sposób mogę zaimplementować „callback” z tego typu rzeczami. Z pewnością byłoby to trudniejsze obejście niż używanie Tasksynchroniczne; 3) Integruję GeneXus ze sterownikiem MongoDB C # , który
udostępnia
1
@ygoe: Użyj blokady zgodnej z asynchronicznie, takiej jak SemaphoreSlim.
Stephen Cleary

Odpowiedzi:

456

Oto obejście, które według mnie działa we wszystkich przypadkach (w tym w zawieszonych dyspozytorach). To nie jest mój kod i wciąż staram się go w pełni zrozumieć, ale działa.

Można go nazwać za pomocą:

customerList = AsyncHelpers.RunSync<List<Customer>>(() => GetCustomers());

Kod jest stąd

public static class AsyncHelpers
{
    /// <summary>
    /// Execute's an async Task<T> method which has a void return value synchronously
    /// </summary>
    /// <param name="task">Task<T> method to execute</param>
    public static void RunSync(Func<Task> task)
    {
        var oldContext = SynchronizationContext.Current;
        var synch = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(synch);
        synch.Post(async _ =>
        {
            try
            {
                await task();
            }
            catch (Exception e)
            {
                synch.InnerException = e;
                throw;
            }
            finally
            {
                synch.EndMessageLoop();
            }
        }, null);
        synch.BeginMessageLoop();

        SynchronizationContext.SetSynchronizationContext(oldContext);
    }

    /// <summary>
    /// Execute's an async Task<T> method which has a T return type synchronously
    /// </summary>
    /// <typeparam name="T">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static T RunSync<T>(Func<Task<T>> task)
    {
        var oldContext = SynchronizationContext.Current;
        var synch = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(synch);
        T ret = default(T);
        synch.Post(async _ =>
        {
            try
            {
                ret = await task();
            }
            catch (Exception e)
            {
                synch.InnerException = e;
                throw;
            }
            finally
            {
                synch.EndMessageLoop();
            }
        }, null);
        synch.BeginMessageLoop();
        SynchronizationContext.SetSynchronizationContext(oldContext);
        return ret;
    }

    private class ExclusiveSynchronizationContext : SynchronizationContext
    {
        private bool done;
        public Exception InnerException { get; set; }
        readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false);
        readonly Queue<Tuple<SendOrPostCallback, object>> items =
            new Queue<Tuple<SendOrPostCallback, object>>();

        public override void Send(SendOrPostCallback d, object state)
        {
            throw new NotSupportedException("We cannot send to our same thread");
        }

        public override void Post(SendOrPostCallback d, object state)
        {
            lock (items)
            {
                items.Enqueue(Tuple.Create(d, state));
            }
            workItemsWaiting.Set();
        }

        public void EndMessageLoop()
        {
            Post(_ => done = true, null);
        }

        public void BeginMessageLoop()
        {
            while (!done)
            {
                Tuple<SendOrPostCallback, object> task = null;
                lock (items)
                {
                    if (items.Count > 0)
                    {
                        task = items.Dequeue();
                    }
                }
                if (task != null)
                {
                    task.Item1(task.Item2);
                    if (InnerException != null) // the method threw an exeption
                    {
                        throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException);
                    }
                }
                else
                {
                    workItemsWaiting.WaitOne();
                }
            }
        }

        public override SynchronizationContext CreateCopy()
        {
            return this;
        }
    }
}
Rachel
źródło
28
Aby zapoznać się z tym, jak to działa, Stephen Toub (Pan Parallel) napisał o tym szereg postów. Część 1 Część 2 Część 3
Cameron MacFarland
18
Zaktualizowałem kod Johna, aby działał bez zawijania zadań w lambdas : github.com/tejacques/AsyncBridge . Zasadniczo pracujesz z blokami asynchronicznymi za pomocą instrukcji using. Wszystko w używanym bloku dzieje się asynchronicznie, z końcem oczekiwania. Minusem jest to, że musisz samodzielnie rozpakować zadanie w ramach wywołania zwrotnego, ale nadal jest dość eleganckie, szczególnie jeśli musisz wywołać kilka funkcji asynchronicznych jednocześnie.
Tom Jacques
17
@StephenCleary Chociaż ja generalnie zgadzam się z tobą, że kod powinien być asynchroniczny całą drogę w dół, czasem znaleźć się w sytuacji, w której niemożliwe jeden ma zmusić go jako synchronicznego połączenia. Zasadniczo moja sytuacja jest taka, że ​​cały mój kod dostępu do danych jest asynchroniczny. Musiałem zbudować mapę witryny na podstawie mapy witryny, a biblioteką innej firmy, z której korzystałem, była MvcSitemap. Teraz, gdy rozszerza się ją za pośrednictwem DynamicNodeProviderBaseklasy podstawowej, nie można zadeklarować jej jako asyncmetody. Albo musiałem zastąpić nową biblioteką, albo po prostu wywołać synchroniczną operację.
justin.lovell
6
@ justin.lovell: Tak, ograniczenia biblioteki mogą zmusić nas do włamania się, przynajmniej do czasu aktualizacji biblioteki. Wygląda na to, że MvcSitemap jest jedną z takich sytuacji, w których wymagany jest hack (filtry MVC i akcje potomne); Właśnie od tego odwieść ludzi w ogóle, ponieważ hacki takie jak ten są używane zbyt często, gdy są nie potrzebne. W szczególności w przypadku MVC niektóre interfejsy API ASP.NET/MVC zakładają, że mają AspNetSynchronizationContext, więc ten konkretny hack nie zadziała, jeśli wywołujesz te interfejsy API.
Stephen Cleary
5
Ten kod nie będzie działał. Jeśli zostanie wywołany z wątku puli, może wywołać zakleszczenie głodu wątku. Dzwoniący zablokuje oczekiwanie na zakończenie operacji, co może się nigdy nie zdarzyć, jeśli wyczerpał pulę wątków. Zobacz ten artykuł .
ZunTzu
318

Informujemy, że ta odpowiedź ma trzy lata. Napisałem go w oparciu o doświadczenie z .Net 4.0, a bardzo mało z 4.5 szczególnie z async-await. Ogólnie rzecz biorąc, jest to ładne proste rozwiązanie, ale czasami psuje rzeczy. Przeczytaj dyskusję w komentarzach.

.Net 4.5

Po prostu użyj tego:

// For Task<T>: will block until the task is completed...
var result = task.Result; 

// For Task (not Task<T>): will block until the task is completed...
task2.RunSynchronously();

Zobacz: TaskAwaiter , Task.Result , Task.RunSynchronously


.Net 4.0

Użyj tego:

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

x.AsyncWaitHandle.WaitOne();

...albo to:

task.Start();
task.Wait();
AK_
źródło
67
.Resultmoże wywołać impas w niektórych scenariuszach
Jordy Langen
122
Resultmogę łatwo spowodować zakleszczenie w asynckodzie , jak opisuję na moim blogu.
Stephen Cleary
8
@StephenCleary Przeczytałem twój post i sam go wypróbowałem. Szczerze mówiąc, myślę, że ktoś w Microsoft był naprawdę pijany ... To ten sam problem, co w przypadku
winformów
9
Pytanie dotyczy zadania zwracanego metodą asynchroniczną. Tego rodzaju zadanie może już rozpoczął, wykonywania lub odwołany, więc korzystanie z Task.RunSynchronously metody może spowodować InvalidOperationException . Zobacz stronę MSDN: Metoda Task.RunSynchronously . Poza tym to zadanie jest prawdopodobnie tworzone przez metody Task.Factory.StartNew lub Task.Run (wewnątrz metody asynchronicznej), więc niebezpiecznie jest spróbować go uruchomić ponownie. Niektóre warunki wyścigu mogą wystąpić w czasie wykonywania. W drugiej ręce Task.Wait i Task.Result mogą spowodować zakleszczenie.
sgnsajgon
4
Uruchom działało synchronicznie dla mnie ... Nie wiem, czy coś pominąłem, ale wydaje się to lepsze niż okropności zaznaczonej odpowiedzi - szukałem właśnie sposobu wyłączenia asynchronizacji podczas testowania kodu, aby zatrzymać interfejs użytkownika z zawieszenia
JonnyRaa
121

Zaskoczony nikt nie wspomniał o tym:

public Task<int> BlahAsync()
{
    // ...
}

int result = BlahAsync().GetAwaiter().GetResult();

Nie tak ładny, jak niektóre inne metody tutaj, ale ma następujące zalety:

  • nie połyka wyjątków (jak Wait)
  • nie zawinie żadnych wyjątków zgłoszonych w AggregateException(jak Result)
  • działa zarówno Taski Task<T>( wypróbuj to sam! )

Ponadto, ponieważ GetAwaiterjest typu kaczego, powinno to działać dla każdego obiektu zwracanego z metody asynchronicznej (jak ConfiguredAwaitablelubYieldAwaitable ), nie tylko Zadania.


edycja: Należy pamiętać, że to podejście (lub użycie .Result) jest możliwe do zakleszczenia, chyba że pamiętasz o dodawaniu za .ConfigureAwait(false)każdym razem, gdy czekasz, dla wszystkich metod asynchronicznych, z których można uzyskać dostęp BlahAsync()(nie tylko te, które wywołuje bezpośrednio). Wyjaśnienie .

// In BlahAsync() body
await FooAsync(); // BAD!
await FooAsync().ConfigureAwait(false); // Good... but make sure FooAsync() and
                                        // all its descendants use ConfigureAwait(false)
                                        // too. Then you can be sure that
                                        // BlahAsync().GetAwaiter().GetResult()
                                        // won't deadlock.

Jeśli jesteś zbyt leniwy, aby dodawać .ConfigureAwait(false)wszędzie i nie zależy ci na wydajności, możesz to zrobić alternatywnie

Task.Run(() => BlahAsync()).GetAwaiter().GetResult()
James Ko
źródło
1
Działa dla mnie dla prostych rzeczy. Ponadto, jeśli metoda zwraca IAsyncOperation, musiałem najpierw przekonwertować ją na zadanie: BlahAsync (). AsTask (). GetAwaiter (). GetResult ();
Lee McPherson,
3
Spowodowało to impas w metodzie internetowej asmx. Niemniej jednak zawinięcie wywołania metody w Task.Run () sprawiło, że zadziałało: Task.Run (() => BlahAsync ()). GetAwaiter (). GetResult ()
Augusto Barreto
Najbardziej podoba mi się to podejście składniowe, ponieważ nie obejmuje lambdas.
dythim
25
NIE edytuj odpowiedzi innych osób, aby wstawić link do własnej. Jeśli uważasz, że Twoja odpowiedź jest lepsza, zostaw ją jako komentarz.
Rachel
1
docs.microsoft.com/en-us/dotnet/api/… mówi o GetAwaiter(): „Ta metoda jest przeznaczona dla użytkownika kompilatora, a nie do bezpośredniego użycia w kodzie”.
Theophilus,
75

O wiele łatwiej jest uruchomić zadanie w puli wątków, niż próbować nakłonić program planujący do uruchomienia go synchronicznie. W ten sposób możesz mieć pewność, że nie zablokuje się. Wpływ na wydajność ma zmiana kontekstu.

Task<MyResult> DoSomethingAsync() { ... }

// Starts the asynchronous task on a thread-pool thread.
// Returns a proxy to the original task.
Task<MyResult> task = Task.Run(() => DoSomethingAsync());

// Will block until the task is completed...
MyResult result = task.Result; 
Michael L. Perry
źródło
3
Następnie wywołujesz task.Wait (). Typ danych to po prostu Zadanie.
Michael L Perry
1
Załóżmy, że DoSomethingAsync () jest długo działającą metodą asynchroniczną jako całością (wewnętrznie oczekuje na długo działające zadanie), ale szybko zwraca kontrolę przepływu do swojego obiektu wywołującego, dlatego praca z argumentem lambda kończy się również szybko. Wynik działania Tusk.Run () może oznaczać Zadanie <Zadanie> lub Zadanie <Zadanie <>> , więc czekasz na wynik zadania zewnętrznego, które zostanie szybko ukończone, ale zadania wewnętrznego (z powodu oczekiwania na długotrwałe zadanie w metodzie asynchronicznej) wciąż działa. Wnioski są takie, że prawdopodobnie musimy zastosować metodę Unwrap () (jak to zrobiono w poście J. J. Lennona), aby osiągnąć synchroniczne zachowanie metody asynchronicznej.
sgnsajgon
5
@sgnsajgon Mylisz się. Task.Run różni się od Task.Factory.StartNew tym, że już automatycznie rozpakowuje wynik. Zobacz ten artykuł .
ZunTzu,
1
Czy mogę po prostu pisać Task.Run(DoSomethingAsync)zamiast tego? To usuwa jeden poziom delegatów.
ygoe
1
Tak. Jednak Task<MyResult> task = Task.Run(async () => await DoSomethingAsync());pójście w przeciwnym kierunku, jak w, jest bardziej wyraźne i odnosi się do obawy @sgnsajgon, że może zwracać Zadanie <Zadanie <Mój Rezultat>>. Prawidłowe przeciążenie Task.Run jest wybierane w obu kierunkach, ale delegat asynchroniczny czyni twój zamiar oczywistym.
Michael L Perry
57

Uczę się o asynchronizacji / czekaniu i wpadłem na sytuację, w której muszę synchronicznie wywołać metodę asynchroniczną. Jak mogę to zrobić?

Najlepszą odpowiedzią jest to, że nie , a szczegóły zależą od tego, jaka jest „sytuacja”.

Czy jest to getter / setter? W większości przypadków lepiej jest mieć metody asynchroniczne niż „właściwości asynchroniczne”. (Aby uzyskać więcej informacji, zobacz mój post na blogu o właściwościach asynchronicznych ).

Czy to aplikacja MVVM i chcesz wykonać asynchroniczne wiązanie danych? Następnie użyj czegoś takiego jak mój NotifyTask, jak opisano w moim artykule MSDN na temat asynchronicznego wiązania danych .

Czy to konstruktor? Prawdopodobnie chcesz rozważyć asynchroniczną metodę fabryczną. (Aby uzyskać więcej informacji, zobacz mój post na blogu o konstruktorach asynchronicznych ).

Prawie zawsze istnieje lepsza odpowiedź niż synchronizacja przez asynchronizację.

Jeśli nie jest to możliwe w Twojej sytuacji (i wiesz o tym, zadając pytanie opisujące sytuację ), zalecam użycie kodu synchronicznego. Asynchronizacja jest najlepsza; synchronizacja do końca jest najlepsza. Synchronizacja nad asynchronią nie jest zalecana.

Istnieje jednak kilka sytuacji, w których konieczna jest synchronizacja przez asynchronizację. Konkretnie, jesteś ograniczony przez kodu wywołującego, dzięki czemu mają być synchronizację (i nie mają żadnego drogę do ponownego przemyślenia lub re-konstrukcja kod, aby umożliwić asynchrony), a ty masz zadzwonić asynchroniczny kodu. Jest to bardzo rzadka sytuacja, ale pojawia się od czasu do czasu.

W takim przypadku musisz użyć jednego z hacków opisanych w moim artykule na temat rozwoju terenów poprzemysłowychasync , a konkretnie:

  • Blokowanie (np GetAwaiter().GetResult().). Pamiętaj, że może to powodować impasy (jak opisuję na moim blogu).
  • Uruchamianie kodu w wątku puli wątków (np. Task.Run(..).GetAwaiter().GetResult() .). Zauważ, że zadziała to tylko wtedy, gdy kod asynchroniczny można uruchomić w wątku puli wątków (tzn. Nie jest zależny od interfejsu użytkownika lub kontekstu ASP.NET).
  • Zagnieżdżone pętle wiadomości. Zauważ, że zadziała to tylko wtedy, gdy kod asynchroniczny zakłada tylko kontekst jednowątkowy, a nie określony typ kontekstu (wiele interfejsu użytkownika i kodu ASP.NET oczekuje określonego kontekstu).

Zagnieżdżone pętle wiadomości są najniebezpieczniejszym ze wszystkich włamań, ponieważ powodują ponowną rozrywkę . Ponowne wejście jest bardzo trudne do uzasadnienia, a (IMO) jest przyczyną większości błędów aplikacji w systemie Windows. W szczególności, jeśli korzystasz z wątku interfejsu użytkownika i blokujesz w kolejce roboczej (czekając na zakończenie pracy asynchronicznej), CLR faktycznie wykonuje pompowanie wiadomości za Ciebie - w rzeczywistości obsłuży niektóre komunikaty Win32 z twojego kod . Aha, i nie masz pojęcia, które wiadomości - kiedy Chris Brumme mówi: „Czy nie byłoby wspaniale wiedzieć, co zostanie napompowane? Niestety, pompowanie jest czarną sztuką, która jest poza śmiertelnym zrozumieniem”., wtedy naprawdę nie mamy nadziei na to, aby wiedzieć.

Tak więc, gdy blokujesz w ten sposób w wątku interfejsu użytkownika, pytasz o problemy. Kolejny cytat z tego samego artykułu: „Od czasu do czasu klienci w firmie lub poza nią odkrywają, że pompujemy wiadomości podczas zarządzanego blokowania STA [wątku interfejsu użytkownika]. Jest to uzasadniony problem, ponieważ wiedzą, że jest to bardzo trudne do pisania solidnego kodu w obliczu ponownego pojawienia się ”.

Tak to jest. Bardzo trudno pisać kod, który jest solidny w obliczu ponownego pojawienia się. Zagnieżdżone pętle wiadomości zmuszają do pisania kodu, który jest solidny w obliczu ponownego pojawienia się. Dlatego przyjęta (i najbardziej pozytywnie oceniona) odpowiedź na to pytanie jest w praktyce bardzo niebezpieczna .

Jeśli całkowicie nie masz żadnych innych opcji - nie możesz przeprojektować kodu, nie możesz go zrestrukturyzować, aby był asynchroniczny - jesteś zmuszony przez niezmienny kod wywołujący do synchronizacji - nie możesz zmienić kodu dalszego do synchronizacji - nie możesz zablokować - nie możesz uruchomić kodu asynchronicznego w osobnym wątku - wtedy i tylko wtedy powinieneś rozważyć przyjęcie ponownego ustalenia.

Jeśli znajdziesz się w tym kącie, polecam użycie czegoś takiego jak Dispatcher.PushFramedo aplikacji WPF , zapętlanie z Application.DoEventsaplikacjami WinForm i, ogólnie, mój własny AsyncContext.Run.

Stephen Cleary
źródło
Stephen jest jeszcze inny bardzo podobny qestion który podałeś zbyt niesamowite odpowiedź. Czy uważasz, że jeden z nich można zamknąć jako duplikat, a może najpierw połączyć żądanie lub wywołać meta (ponieważ każde q ma ~ 200 000 wyświetleń ponad 200 głosów)? Propozycje?
Aleksiej Lewenkow
1
@AlexeiLevenkov: Nie mam racji, robiąc to z kilku powodów: 1) Odpowiedź na powiązane pytanie jest dość nieaktualna. 2) Napisałem cały artykuł na ten temat, który moim zdaniem jest bardziej kompletny niż jakikolwiek istniejący SO Q / A. 3) Przyjęta odpowiedź na to pytanie jest niezwykle popularna. 4) Jestem zdecydowanie przeciwny tej przyjętej odpowiedzi. Zatem zamknięcie tego jako duplikatu byłoby nadużyciem władzy; zamknięcie tego jako duplikat tego (lub połączenie) dałoby niebezpieczną odpowiedź jeszcze bardziej. Pozwalam i zostawiam to społeczności.
Stephen Cleary
Ok. Zastanowię się nad podniesieniem go na meta niż w jakiś sposób.
Aleksiej Lewenkow
9
Ta odpowiedź jest bardzo ważna. „Użyj asynchronizacji do końca” jest mylącą poradą, ponieważ najwyraźniej nie można jej zastosować. Program z Main()metodą asynchroniczną nie kompiluje się; w pewnym momencie już dostał się do wypełnienia luki między synchronizacji i async światów. To nie jestbardzo rzadka sytuacja” , jest niezbędna dosłownie w każdym programie, który wywołuje metodę asynchroniczną. Nie ma opcji, aby nie wykonywać synchronizacji przez asynchronizację” , tylko opcja przerzucenia obciążenia na metodę wywoływania zamiast narzucania jej w tej, którą właśnie piszesz.
Mark Amery
1
Świetny. Zaraz zastosuję asyncwszystkie metody w mojej aplikacji. I to dużo. Czy to nie może być domyślne?
ygoe
25

Jeśli dobrze czytam twoje pytanie - kod, który chce synchronicznego wywołania metody asynchronicznej, jest wykonywany w zawieszonym wątku programu rozsyłającego. I chcesz faktycznie synchronicznie zablokować ten wątek, dopóki metoda asynchroniczna nie zostanie zakończona.

Metody asynchroniczne w C # 5 są oparte na skutecznym pocięciu metody na kawałki pod maską i zwróceniu metody, Taskktóra może śledzić ogólne zakończenie całego shabang. Jednak sposób wykonania podzielonych metod może zależeć od typu wyrażenia przekazanego doawait operatorowi.

Przez większość czasu będziesz używać awaitwyrażenia typu Task. Implementacja awaitwzorca zadania jest „inteligentna”, ponieważ odsyła do SynchronizationContext, co w zasadzie powoduje, że zdarzają się:

  1. Jeśli wątek wchodzi do await jest w wątku pętli komunikatów programu Dispatcher lub WinForms, zapewnia to, że fragmenty metody asynchronicznej występują w ramach przetwarzania kolejki komunikatów.
  2. Jeśli wątek wchodzący do wątku awaitznajduje się w wątku puli wątków, pozostałe fragmenty metody asynchronicznej występują w dowolnym miejscu w puli wątków.

Dlatego prawdopodobnie masz problemy - implementacja metody asynchronicznej próbuje uruchomić resztę w programie Dispatcher - nawet jeśli jest zawieszona.

... tworzenie kopii zapasowej! …

Muszę zadać pytanie, dlaczego próbujesz synchronicznie blokować metodę asynchroniczną? Takie postępowanie zniweczyłoby cel, dla którego metoda chciała być wywoływana asynchronicznie. Ogólnie rzecz biorąc, kiedy zaczniesz używać awaitmetody Dispatcher lub interfejsu użytkownika, będziesz chciał zmienić cały asynchroniczny przepływ interfejsu użytkownika. Na przykład, jeśli twój callstack był podobny do następującego:

  1. [Top] WebRequest.GetResponse()
  2. YourCode.HelperMethod()
  3. YourCode.AnotherMethod()
  4. YourCode.EventHandlerMethod()
  5. [UI Code].Plumbing()- WPFlub WinFormsKod
  6. [Pętla wiadomości] - WPFlub WinFormsMessage Loop

Następnie, gdy kod zostanie przekształcony w celu użycia asynchronizacji, zwykle kończy się to

  1. [Top] WebRequest.GetResponseAsync()
  2. YourCode.HelperMethodAsync()
  3. YourCode.AnotherMethodAsync()
  4. YourCode.EventHandlerMethodAsync()
  5. [UI Code].Plumbing() - WPFlub WinFormsKod
  6. [Message Loop] - WPFlub WinFormsMessage Loop

Właściwie odpowiadanie

Powyższa klasa AsyncHelpers faktycznie działa, ponieważ zachowuje się jak zagnieżdżona pętla komunikatów, ale instaluje własną funkcję równoległą w programie Dispatcher, zamiast próbować wykonać go w samym programie Dispatcher. To jedno obejście problemu.

Innym obejściem jest wykonanie metody asynchronicznej w wątku puli wątków, a następnie poczekanie na jej zakończenie. Jest to łatwe - możesz to zrobić za pomocą następującego fragmentu kodu:

var customerList = TaskEx.RunEx(GetCustomers).Result;

Ostatecznym interfejsem API będzie Task.Run (...), ale przy CTP potrzebne będą sufiksy Ex ( wyjaśnienie tutaj ).

Theo Yaung
źródło
+1 za szczegółowe wyjaśnienie, jednak TaskEx.RunEx(GetCustomers).Resultzawiesza aplikację, gdy zostanie uruchomiona w zawieszonym wątku dyspozytora. Ponadto metoda GetCustomers () jest zwykle uruchamiana asynchronicznie, jednak w jednej sytuacji musi działać synchronicznie, więc szukałem sposobu, aby to zrobić bez budowania wersji synchronicznej metody.
Rachel
+1 za „dlaczego próbujesz synchronicznie blokować metodę asynchroniczną?” Zawsze istnieje sposób na prawidłowe użycie asyncmetod; z pewnością należy unikać zagnieżdżonych pętli.
Stephen Cleary
24

To działa dobrze dla mnie

public static class TaskHelper
{
    public static void RunTaskSynchronously(this Task t)
    {
        var task = Task.Run(async () => await t);
        task.Wait();
    }

    public static T RunTaskSynchronously<T>(this Task<T> t)
    {
        T res = default(T);
        var task = Task.Run(async () => res = await t);
        task.Wait();
        return res;
    }
}
Łaskawy
źródło
Musisz także użyć metody Task.Unwrap , ponieważ instrukcja Task.Wait powoduje oczekiwanie na zadanie zewnętrzne (utworzone przez Task.Run ), a nie na wewnętrzne oczekiwanie na zadanie przekazane jako parametr metody rozszerzenia. Twoja metoda Task.Run zwraca nie zadanie <T>, ale zadanie <Zadanie <T>>. W niektórych prostych scenariuszach Twoje rozwiązanie może działać z powodu optymalizacji TaskScheduler , na przykład przy użyciu metody TryExecuteTaskInline do wykonywania zadań w bieżącym wątku podczas operacji oczekiwania. Proszę spojrzeć na mój komentarz do tej odpowiedzi.
sgnsajgon
1
To nie jest poprawne. Task.Run zwróci Task <T>. Zobacz to przeciążenie msdn.microsoft.com/en-us/library/hh194918(v=vs.110).aspx
Clement
Jak należy to wykorzystać? Ten impas w WPF:MyAsyncMethod().RunTaskSynchronously();
ygoe
18

Najprostszym sposobem, jaki znalazłem, aby uruchamiać zadanie synchronicznie i bez blokowania wątku interfejsu użytkownika jest użycie RunSynchronously () w następujący sposób:

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

W moim przypadku zdarzenie jest uruchamiane, gdy coś się wydarzy. Nie wiem, ile razy to nastąpi. Używam powyższego kodu w moim zdarzeniu, więc za każdym razem, gdy się uruchamia, tworzy zadanie. Zadania są wykonywane synchronicznie i działa świetnie dla mnie. Byłem po prostu zaskoczony, że tak długo zajęło mi odkrycie tego, biorąc pod uwagę, jak to jest proste. Zazwyczaj zalecenia są znacznie bardziej złożone i podatne na błędy. To było proste i czyste.

piksel
źródło
1
Ale jak możemy użyć tej metody, gdy kod asynchroniczny zwraca coś, czego potrzebujemy?
S.Serpooshan
16

Stawiłem temu czoła kilka razy, głównie podczas testów jednostkowych lub w rozwoju usług Windows. Obecnie zawsze korzystam z tej funkcji:

        var runSync = Task.Factory.StartNew(new Func<Task>(async () =>
        {
            Trace.WriteLine("Task runSync Start");
            await TaskEx.Delay(2000); // Simulates a method that returns a task and
                                      // inside it is possible that there
                                      // async keywords or anothers tasks
            Trace.WriteLine("Task runSync Completed");
        })).Unwrap();
        Trace.WriteLine("Before runSync Wait");
        runSync.Wait();
        Trace.WriteLine("After runSync Waited");

To proste, łatwe i nie miałem problemów.

J. Lennon
źródło
To jedyny, który nie był dla mnie impasem.
AndreFeijo
15

Znalazłem ten kod w Microsoft.AspNet.Identity.Core i działa.

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

// Microsoft.AspNet.Identity.AsyncHelper
public static TResult RunSync<TResult>(Func<Task<TResult>> func)
{
    CultureInfo cultureUi = CultureInfo.CurrentUICulture;
    CultureInfo culture = CultureInfo.CurrentCulture;
    return AsyncHelper._myTaskFactory.StartNew<Task<TResult>>(delegate
    {
        Thread.CurrentThread.CurrentCulture = culture;
        Thread.CurrentThread.CurrentUICulture = cultureUi;
        return func();
    }).Unwrap<TResult>().GetAwaiter().GetResult();
}
wenhx
źródło
13

Mała uwaga - takie podejście:

Task<Customer> task = GetCustomers();
task.Wait()

działa dla WinRT.

Pozwól mi wyjaśnić:

private void TestMethod()
{
    Task<Customer> task = GetCustomers(); // call async method as sync and get task as result
    task.Wait(); // wait executing the method
    var customer = task.Result; // get's result.
    Debug.WriteLine(customer.Name); //print customer name
}
public class Customer
{
    public Customer()
    {
        new ManualResetEvent(false).WaitOne(TimeSpan.FromSeconds(5));//wait 5 second (long term operation)
    }
    public string Name { get; set; }
}
private Task<Customer> GetCustomers()
{
    return Task.Run(() => new Customer
    {
        Name = "MyName"
    });
}

Co więcej, to podejście działa tylko w przypadku rozwiązań Windows Store!

Uwaga: Ten sposób nie jest bezpieczny dla wątków, jeśli wywołasz metodę w innej metodzie asynchronicznej (zgodnie z komentarzami @Servy)

RredCat
źródło
Wyjaśniłem to rozwiązanie, sprawdź sekcję EDYCJA.
RredCat,
2
Może to bardzo łatwo doprowadzić do zakleszczenia, gdy zostanie wywołany w sytuacjach asynchronicznych.
Servy,
@Servy ma sens. Więc jak się poprawię używając Wait (timeOut) może pomóc, prawda?
RredCat,
1
Następnie musisz się martwić, że limit czasu zostanie osiągnięty, gdy operacja nie zostanie faktycznie wykonana, co jest bardzo złe, a także czas spędzony na oczekiwaniu na limit czasu w przypadkach, w których jest on zakleszczony (w takim przypadku nadal trwasz kiedy nie jest zrobione). Więc nie, to nie rozwiązuje problemu.
Servy,
@Servy Wygląda na to, że muszę wdrożyć CancellationTokenmoje rozwiązanie.
RredCat,
10

W twoim kodzie pierwsze oczekiwanie na wykonanie zadania, ale jeszcze go nie uruchomiłeś, więc czeka ono w nieskończoność. Spróbuj tego:

Task<Customer> task = GetCustomers();
task.RunSynchronously();

Edytować:

Mówisz, że dostajesz wyjątek. Proszę zamieścić więcej szczegółów, w tym ślad stosu.
Mono zawiera następujący przypadek testowy:

[Test]
public void ExecuteSynchronouslyTest ()
{
        var val = 0;
        Task t = new Task (() => { Thread.Sleep (100); val = 1; });
        t.RunSynchronously ();

        Assert.AreEqual (1, val);
}

Sprawdź, czy to Ci odpowiada. Jeśli tak się nie stanie, choć bardzo mało prawdopodobne, możesz mieć dziwną kompilację Async CTP. Jeśli to działa, możesz sprawdzić, co dokładnie generuje kompilator i czym Taskróżni się tworzenie instancji od tej próbki.

Edytuj # 2:

Sprawdziłem w Reflector, czy opisany wyjątek występuje, gdy m_actionjest null. To trochę dziwne, ale nie jestem ekspertem od Async CTP. Jak powiedziałem, powinieneś zdekompilować swój kod i zobaczyć, jak dokładnie Tasktworzona jest instancja, niezależnie od tego, jak m_actionjest null.


PS O co chodzi z okazjonalnymi głosowaniami? Możesz rozwinąć temat?

Dan Abramov
źródło
Dostosowałem swoje pytanie, aby kod, który próbowałem, był nieco jaśniejszy. RunSynchronously zwraca błąd o wartości RunSynchronously may not be called on a task unbound to a delegate. Google nie pomaga, ponieważ wszystkie wyniki są po chińsku ...
Rachel
Myślę, że różnica polega na tym, że nie tworzę zadania, a następnie próbuję je uruchomić. Zamiast tego zadanie jest tworzone metodą asynchroniczną, gdy awaitużywane jest słowo kluczowe. Wyjątkiem opublikowanym w moim wcześniejszym komentarzu jest wyjątek, który otrzymuję, chociaż jest to jeden z niewielu, których nie mogę znaleźć w Google i znaleźć przyczynę lub rozwiązanie.
Rachel
1
asynca asyncsłowa kluczowe są niczym więcej niż cukrem składniowym. Kompilator generuje kod, aby utworzyć Task<Customer>w GetCustomers()tak to gdzie będę szukać w pierwszej kolejności. Jeśli chodzi o wyjątek, wysłałeś tylko komunikat wyjątku, który jest bezużyteczny bez typu wyjątku i śledzenia stosu. Wywołaj ToString()metodę wyjątku i wyślij wynik w pytaniu.
Dan Abramov,
@gaearon: W moim pierwotnym pytaniu opublikowałem szczegóły wyjątku i ślad stosu.
Rachel
2
@gaearon Myślę, że masz negatywne opinie, ponieważ twój post nie dotyczy pytań. Dyskusja dotyczy metod asynchronicznych, a nie prostych metod zwracania zadań. Co więcej, moim zdaniem, mechanizm asynchronicznego oczekiwania jest cukrem składniowym, ale nie tak trywialnym - istnieje kontynuacja, przechwytywanie kontekstu, wznawianie kontekstu lokalnego, ulepszona obsługa lokalnych wyjątków i wiele więcej. Następnie nie powinieneś wywoływać metody RunSynchronously na podstawie metody asynchronicznej, ponieważ z definicji metoda asynchroniczna powinna zwracać Zadanie, które jest obecnie co najmniej zaplanowane i więcej niż raz jest w stanie uruchomionym.
sgnsajgon
9

Testowane w .Net 4.6. Może także uniknąć impasu.

Do zwrotu metody asynchronicznej Task.

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

Do zwrotu metody asynchronicznej Task<T>

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

Edytuj :

Jeśli program wywołujący działa w wątku puli wątków (lub program wywołujący również wykonuje zadanie), może nadal powodować impas w niektórych sytuacjach.

Liang
źródło
1
Moja answar po prawie 8 latach :) Drugi przykład - spowoduje impas we wszystkich zaplanowanych kontekstach, które są głównie używane (aplikacja na konsolę / .NET core / aplikacja na komputer / ...). tutaj masz więcej informacji o tym, o czym teraz mówię: medium.com/rubrikkgroup/…
W92
Resultjest idealny do pracy, jeśli chcesz synchronicznego połączenia, a wręcz niebezpieczny w przeciwnym razie. W nazwie Resultani w jej inteligencji nic nie Resultwskazuje na to, że jest to połączenie blokujące. Naprawdę należy go zmienić.
Zodman
5

użyj poniżej fragmentu kodu

Task.WaitAll(Task.Run(async () => await service.myAsyncMethod()));
Mahesh
źródło
4

Dlaczego nie utworzyć połączenia takiego jak:

Service.GetCustomers();

to nie jest asynchroniczne.

Daniel A. White
źródło
4
Tak właśnie zrobię, jeśli nie będę w stanie tego zrobić ... oprócz wersji Async utwórz wersję Sync
Rachel
3

Ta odpowiedź jest przeznaczona dla każdego, kto używa WPF dla .NET 4.5.

Jeśli spróbujesz wykonać Task.Run()w wątku GUI, task.Wait()zawiesi się na czas nieokreślony, jeśli nie masz asyncsłowa kluczowego w definicji funkcji.

Ta metoda rozszerzenia rozwiązuje problem, sprawdzając, czy jesteśmy w wątku GUI, a jeśli tak, uruchamiając zadanie w wątku programu rozsyłającego WPF.

Ta klasa może działać jak klej między światem asynchronicznym / oczekującym a światem nie asynchronicznym / oczekującym w sytuacjach, gdy jest to nieuniknione, takich jak właściwości MVVM lub zależności od innych interfejsów API, które nie używają asynchronicznego / oczekującego.

/// <summary>
///     Intent: runs an async/await task synchronously. Designed for use with WPF.
///     Normally, under WPF, if task.Wait() is executed on the GUI thread without async
///     in the function signature, it will hang with a threading deadlock, this class 
///     solves that problem.
/// </summary>
public static class TaskHelper
{
    public static void MyRunTaskSynchronously(this Task task)
    {
        if (MyIfWpfDispatcherThread)
        {
            var result = Dispatcher.CurrentDispatcher.InvokeAsync(async () => { await task; });
            result.Wait();
            if (result.Status != DispatcherOperationStatus.Completed)
            {
                throw new Exception("Error E99213. Task did not run to completion.");
            }
        }
        else
        {
            task.Wait();
            if (task.Status != TaskStatus.RanToCompletion)
            {
                throw new Exception("Error E33213. Task did not run to completion.");
            }
        }
    }

    public static T MyRunTaskSynchronously<T>(this Task<T> task)
    {       
        if (MyIfWpfDispatcherThread)
        {
            T res = default(T);
            var result = Dispatcher.CurrentDispatcher.InvokeAsync(async () => { res = await task; });
            result.Wait();
            if (result.Status != DispatcherOperationStatus.Completed)
            {
                throw new Exception("Error E89213. Task did not run to completion.");
            }
            return res;
        }
        else
        {
            T res = default(T);
            var result = Task.Run(async () => res = await task);
            result.Wait();
            if (result.Status != TaskStatus.RanToCompletion)
            {
                throw new Exception("Error E12823. Task did not run to completion.");
            }
            return res;
        }
    }

    /// <summary>
    ///     If the task is running on the WPF dispatcher thread.
    /// </summary>
    public static bool MyIfWpfDispatcherThread
    {
        get
        {
            return Application.Current.Dispatcher.CheckAccess();
        }
    }
}
Contango
źródło
3

Wystarczy zadzwonić .Result;lub .Wait()jest to ryzyko impasu, jak wielu powiedziało w komentarzach. Ponieważ większość z nas lubi onelinerów, możesz z nich korzystać.Net 4.5<

Pozyskiwanie wartości metodą asynchroniczną:

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

Synchroniczne wywoływanie metody asynchronicznej

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

Z powodu użycia nie wystąpią problemy z zakleszczeniem Task.Run.

Źródło:

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

Ogglas
źródło
1

Myślę, że następująca metoda pomocnicza może również rozwiązać problem.

private TResult InvokeAsyncFuncSynchronously<TResult>(Func< Task<TResult>> func)
    {
        TResult result = default(TResult);
        var autoResetEvent = new AutoResetEvent(false);

        Task.Run(async () =>
        {
            try
            {
                result = await func();
            }
            catch (Exception exc)
            {
                mErrorLogger.LogError(exc.ToString());
            }
            finally
            {
                autoResetEvent.Set();
            }
        });
        autoResetEvent.WaitOne();

        return result;
    }

Można go użyć w następujący sposób:

InvokeAsyncFuncSynchronously(Service.GetCustomersAsync);
donttellya
źródło
1
Proszę wyjaśnić sposób głosowania
donttellya
2
... nadal jestem chętnie zainteresowany, dlaczego ta odpowiedź została odrzucona?
donttellya
Nie jest to prawda „synchronicznie”. Tworzysz dwa wątki i czekasz na pierwsze wyniki drugiego.
tmt
a poza tym, to bardzo zły pomysł.
Dan Pantry
1
Właśnie napisałem prawie identyczny kod (taki sam wiersz po wierszu), ale zamiast tego użyłem SemaphoreSlim zamiast zdarzenia automatycznego resetu. Szkoda, że ​​nie widziałem tego wcześniej. Uważam to podejście za zapobiegające zakleszczeniom i utrzymujące działanie kodu asynchronicznego tak samo, jak w prawdziwych scenariuszach asynchronicznych. Nie bardzo wiem, dlaczego to zły pomysł. Wydaje się znacznie czystszy niż inne podejścia, które widziałem powyżej.
tmrog,
0

To działa dla mnie

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

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

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

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

    class SomeClass
    {
        public async Task<object> LoginAsync(object loginInfo)
        {
            return await Task.FromResult(0);
        }
        public object Login(object loginInfo)
        {
            return AsyncHelper.RunSync(() => LoginAsync(loginInfo));
            //return this.LoginAsync(loginInfo).Result.Content;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var someClass = new SomeClass();

            Console.WriteLine(someClass.Login(1));
            Console.ReadLine();
        }
    }
}
Dan Nguyen
źródło
-1

Przekonałem się, że SpinWait działa w tym przypadku całkiem dobrze.

var task = Task.Run(()=>DoSomethingAsyncronous());

if(!SpinWait.SpinUntil(()=>task.IsComplete, TimeSpan.FromSeconds(30)))
{//Task didn't complete within 30 seconds, fail...
   return false;
}

return true;

Powyższe podejście nie wymaga użycia .Result lub .Wait (). Pozwala także określić limit czasu, aby nie utknąć na zawsze, na wypadek gdyby zadanie nigdy się nie zakończyło.

Curtis
źródło
1
Downvote sugeruje, że ktoś nie lubi tej metody. Czy jest ktoś, kto może skomentować tę wadę?
Grax32
W przypadku braku downvoter mówiącego DLACZEGO dany głos został oddany, czy ktoś może go głosować? :-)
Curtis,
1
To jest odpytywanie (wirowanie), delegat będzie pobierał wątek z puli do 1000 razy na sekundę. Może nie zwrócić kontroli natychmiast po zakończeniu zadania (błąd do 10 + ms ). Jeśli zakończy się ono po przekroczeniu limitu czasu, zadanie będzie kontynuowane, co powoduje, że przekroczenie limitu czasu jest praktycznie bezużyteczne.
Sinatr
Właściwie używam tego w całym kodzie i kiedy warunek jest spełniony, SpinWaitSpinUntil () natychmiast kończy działanie. Niezależnie od tego, co nastąpi wcześniej, „warunek spełniony” lub limit czasu, zadanie kończy się. Nie działa dalej.
Curtis
-3

Na wp8:

Zawiń to:

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

Nazwać:

GetCustomersSynchronously();
użytkownik2113284
źródło
3
Nie, to przyzwyczajenie praca, bo zadanie nie czekają delegata od konstruktora (jego delegatem, a nie zadanie ..)
Rico Suter
-4
    private int GetSync()
    {
        try
        {
            ManualResetEvent mre = new ManualResetEvent(false);
            int result = null;

            Parallel.Invoke(async () =>
            {
                result = await SomeCalcAsync(5+5);
                mre.Set();
            });

            mre.WaitOne();
            return result;
        }
        catch (Exception)
        {
            return null;
        }
    }
ksemenenko
źródło
-5

Lub możesz po prostu iść z:

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

Aby to skompilować, upewnij się, że odwołujesz się do zestawu rozszerzeń:

System.Net.Http.Formatting
użytkownik2057962
źródło
-9

Wypróbuj następujący kod, który działa dla mnie:

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

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

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

            taskSearchRecureList = null;

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

            this.SetOnAdapter(taskSearchRecureList);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("ActivityTaskFooter -> TaskSearchOnTaskList:" + ex.Message);
    }
}
gandhraj gayakwad
źródło