Jak i kiedy używać „asynchronizacji” i „czekania”


1065

Z mojego zrozumienia jednej z głównych rzeczy, które asynciawait zrobić, jest wykonanie kodu łatwo pisać i czytać - ale jest ich użyciem równać do tarła wątki tła wykonywać długiego trwania logiki?

Obecnie wypróbowuję najbardziej podstawowy przykład. Dodałem kilka komentarzy. Czy możesz mi to wyjaśnić?

// I don't understand why this method must be marked as `async`.
private async void button1_Click(object sender, EventArgs e)
{
    Task<int> access = DoSomethingAsync();
    // task independent stuff here

    // this line is reached after the 5 seconds sleep from 
    // DoSomethingAsync() method. Shouldn't it be reached immediately? 
    int a = 1; 

    // from my understanding the waiting should be done here.
    int x = await access; 
}

async Task<int> DoSomethingAsync()
{
    // is this executed on a background thread?
    System.Threading.Thread.Sleep(5000);
    return 1;
}

48
Ponadto w swoim przykładzie zwróć uwagę, że podczas kompilowania powyższego kodu pojawia się ostrzeżenie. Zwróć uwagę na ostrzeżenie . Mówi ci, że ten kod nie ma sensu.
Eric Lippert

Odpowiedzi:


759

Podczas używania asynci awaitkompilator generuje maszynę stanu w tle.

Oto przykład, na którym, mam nadzieję, wyjaśnię niektóre szczegóły dotyczące wysokiego poziomu:

public async Task MyMethodAsync()
{
    Task<int> longRunningTask = LongRunningOperationAsync();
    // independent work which doesn't need the result of LongRunningOperationAsync can be done here

    //and now we call await on the task 
    int result = await longRunningTask;
    //use the result 
    Console.WriteLine(result);
}

public async Task<int> LongRunningOperationAsync() // assume we return an int from this long running operation 
{
    await Task.Delay(1000); // 1 second delay
    return 1;
}

OK, więc co się tutaj dzieje:

  1. Task<int> longRunningTask = LongRunningOperationAsync(); zaczyna się wykonywać LongRunningOperation

  2. Niezależna praca jest wykonywana, załóżmy, że główny wątek (identyfikator wątku = 1) await longRunningTaskzostanie osiągnięty.

    Teraz, jeśli longRunningTasknie zakończyło się i nadal działa, MyMethodAsync()powróci do metody wywoływania, więc główny wątek nie zostanie zablokowany. Po zakończeniu longRunningTaskwątek z ThreadPool (może być dowolnym wątkiem) powróci do MyMethodAsync()poprzedniego kontekstu i będzie kontynuował wykonywanie (w tym przypadku drukowanie wyniku na konsoli).

Drugi przypadek polegałby na tym, że proces longRunningTaskjuż zakończył się, a wynik jest dostępny. Po osiągnięciu tego await longRunningTaskmamy już wynik, więc kod będzie nadal działał w tym samym wątku. (w tym przypadku wynik drukowania do konsoli). Oczywiście nie jest tak w przypadku powyższego przykładu, w którym jest Task.Delay(1000)zaangażowany.


65
Dlaczego mamy „czekać” z „Task.Delay (1000);” w metodzie asynchronicznej LongRunningOperation?
Benison Sam

3
@codea W komentarzach Erica Lipperta do artykułu powiązał artykuł wprowadzający z tym tematem, w którym porównuje strategię DoEvents z asynchronicznym oczekiwaniem
Camilo Martinez

13
@BenisonSam wątek jest trochę stary, ale miałem to samo pytanie i szukałem odpowiedzi. Powodem „oczekiwania” jest to, że jeśli pominiemy „oczekiwanie”, funkcja LongRunningOperationAsync () natychmiast powróci. W rzeczywistości kompilator wyświetli ostrzeżenie, jeśli usuniemy oczekiwanie. Blog na blogu Stephena Clearygo blog.stephencleary.com/2011/09/... zawiera informacje na temat dyskusji na temat projektu.
shelbypereira,

70
Jeśli każda metoda asynchroniczna musi mieć w środku oczekiwanie, a oczekiwania można dokonać tylko w przypadku metod z asynchronią, kiedy to się kończy?
Bruno Santos

108
Ta odpowiedź jest wyraźnie błędna. Te wiele pozytywnych opinii spowoduje nieprawidłowe zrozumienie dla wielu użytkowników. Dokumentacja MS wyraźnie mówi, że żaden inny wątek nie jest używany podczas korzystania z asynchronizacji, czekaj. msdn.microsoft.com/en-us/library/mt674882.aspx Proszę o poprawienie odpowiedzi. Z tego powodu zmarnowałem cały dzień.
Krishna Deepak

171

Z mojego zrozumienia jedną z głównych rzeczy, które asynchronizują i czekają, jest ułatwienie pisania i czytania kodu.

Mają na celu ułatwienie pisania i czytania kodu asynchronicznego , tak.

Czy jest to to samo, co spawnowanie wątków tła w celu wykonania logiki o długim czasie trwania?

Ani trochę.

// Nie rozumiem, dlaczego ta metoda musi być oznaczona jako „asynchroniczna”.

Słowo asynckluczowe włącza awaitsłowo kluczowe. Tak więc każda zastosowana metoda awaitmusi zostać oznaczona async.

// Ten wiersz jest osiągany po 5 sekundach uśpienia od metody DoSomethingAsync (). Czy nie należy tego osiągnąć natychmiast?

Nie, ponieważ asyncmetody nie są domyślnie uruchamiane w innym wątku.

// Czy jest to wykonywane w wątku w tle?

Nie.


Pomocne może okazać się moje async/ awaitwprowadzenie . W oficjalnych docs MSDN są również wyjątkowo dobre (szczególnie TAP fragment), a asynczespół zgasić doskonałą nas .


6
Więc nie działa na wątku w tle, ale również nie blokuje. Jest to możliwe dzięki asynchronicznym interfejsom API, które używają wywołań zwrotnych zamiast żonglować wątkami. Zainicjujesz operację (I / O, gniazdo, ...) i powrócisz do robienia swoich rzeczy. Po zakończeniu operacji system operacyjny wywoła wywołanie zwrotne. Tak właśnie działa Node.js lub framework Python Twisted i mają one również ładne wyjaśnienia.
Roman Plášil,

3
„Słowo kluczowe async włącza słowo kluczowe Oczekiwanie. Dlatego każda metoda wykorzystująca Oczekiwanie musi być oznaczona jako asynchroniczna.”, - ale dlaczego? ta odpowiedź nie pomaga zrozumieć, dlaczego metodę należy oznaczyć jako asynchroniczną. Czy kompilator nie może po prostu wywnioskować, że metoda jest asynchroniczna, szukając w środku oczekujących słów kluczowych?
Stanislav

9
@Stanislav: Mam wpis na blogu, który dotyczy tego pytania.
Stephen Cleary

3
Sugerowane wyjaśnienie: Nie, ponieważ asyncmetody nie są domyślnie uruchamiane w innym wątku. W twoim przykładzie Sleep()wywołanie wewnątrz DoSomethingAsync()blokuje bieżący wątek, co uniemożliwia kontynuowanie wykonywania button1_Click()do momentu DoSomethingAsync()zakończenia. Zwróć uwagę, że podczas gdy Thread.Sleep()blokuje wykonywany wątek,Task.Delay() does not.
DavidRR

166

Wyjaśnienie

Oto szybki przykład async/ awaitna wysokim poziomie. Jest o wiele więcej szczegółów do rozważenia poza tym.

Uwaga: Task.Delay(1000)symuluje wykonywanie pracy przez 1 sekundę. Myślę, że najlepiej myśleć o tym jako o czekaniu na odpowiedź z zewnętrznego źródła. Ponieważ nasz kod czeka na odpowiedź, system może odłożyć uruchomione zadanie na bok i wrócić do niego po zakończeniu. Tymczasem może wykonać inne prace nad tym wątkiem.

W poniższym przykładzie robi to dokładnie pierwszy blok . Natychmiast rozpoczyna wszystkie zadania ( Task.Delaylinie) i odsuwa je na bok. Kod zatrzyma się na await alinii, dopóki nie upłynie 1 sekundowe opóźnienie przed przejściem do następnej linii. Ponieważ b, c, d, a ewszystko zaczęło się wykonanie w prawie dokładnie w tym samym czasie, co a(z powodu braku czekają), powinny zakończyć się w przybliżeniu w tym samym czasie w tej sprawie.

W poniższym przykładzie drugi blok uruchamia zadanie i czeka na jego zakończenie (czyli co się awaitdzieje) przed rozpoczęciem kolejnych zadań. Każda iteracja zajmuje 1 sekundę. awaitJest wstrzymywanie programu i czeka na wynik przed kontynuowaniem. Jest to główna różnica między pierwszym a drugim blokiem.

Przykład

Console.WriteLine(DateTime.Now);

// This block takes 1 second to run because all
// 5 tasks are running simultaneously
{
    var a = Task.Delay(1000);
    var b = Task.Delay(1000);
    var c = Task.Delay(1000);
    var d = Task.Delay(1000);
    var e = Task.Delay(1000);

    await a;
    await b;
    await c;
    await d;
    await e;
}

Console.WriteLine(DateTime.Now);

// This block takes 5 seconds to run because each "await"
// pauses the code until the task finishes
{
    await Task.Delay(1000);
    await Task.Delay(1000);
    await Task.Delay(1000);
    await Task.Delay(1000);
    await Task.Delay(1000);
}
Console.WriteLine(DateTime.Now);

WYNIK:

5/24/2017 2:22:50 PM
5/24/2017 2:22:51 PM (First block took 1 second)
5/24/2017 2:22:56 PM (Second block took 5 seconds)

Dodatkowe informacje dotyczące SynchronizationContext

Uwaga: w tym momencie sprawy stają się dla mnie trochę mgliste, więc jeśli coś jest w błędzie, popraw mnie, a ja zaktualizuję odpowiedź. Ważne jest, aby mieć podstawową wiedzę na temat tego, jak to działa, ale możesz sobie z tym poradzić, nie będąc ekspertem w tej dziedzinie, dopóki nie będziesz go używać ConfigureAwait(false), chociaż prawdopodobnie stracisz szansę na optymalizację, zakładam.

Jest jeden aspekt tego, co sprawia, że koncepcja async/ jest awaitnieco trudniejsza do zrozumienia. Faktem jest, że w tym przykładzie wszystko dzieje się w tym samym wątku (lub przynajmniej w tym samym wątku, który wydaje się być tym samym wątkiem SynchronizationContext). Domyślnie awaitprzywróci kontekst synchronizacji oryginalnego wątku, na którym był uruchomiony. Na przykład w ASP.NET masz HttpContextpowiązanie z wątkiem, gdy przychodzi żądanie. Ten kontekst zawiera rzeczy specyficzne dla oryginalnego żądania HTTP, takie jak oryginalny obiekt żądania, który ma takie elementy jak język, adres IP, nagłówki itp. Jeśli przełączysz wątki w połowie przetwarzania, możesz potencjalnie skończyć z próbą wyciągnięcia informacji z tego obiektu na inny obiektHttpContextco może być katastrofalne. Jeśli wiesz, że do niczego nie będziesz używać kontekstu, możesz „nie przejmować się” tym. Zasadniczo pozwala to na uruchamianie kodu w osobnym wątku bez konieczności przybliżania kontekstu.

Jak to osiągasz? Domyślnie await a;kod faktycznie zakłada, że ​​chcesz przechwycić i przywrócić kontekst:

await a; //Same as the line below
await a.ConfigureAwait(true);

Jeśli chcesz pozwolić głównemu kodowi na kontynuowanie w nowym wątku bez oryginalnego kontekstu, po prostu użyj false zamiast true, aby wiedzieć, że nie musi on przywracać kontekstu.

await a.ConfigureAwait(false);

Po zakończeniu pauzy program będzie kontynuował potencjalnie na zupełnie innym wątku z innym kontekstem. Stąd pochodziłaby poprawa wydajności - mogłaby być kontynuowana w dowolnym dostępnym wątku bez konieczności przywracania pierwotnego kontekstu, z którym zaczęła.

Czy to jest mylące? O tak! Czy potrafisz to rozgryźć? Prawdopodobnie! Po zapoznaniu się z pojęciami przejdź do wyjaśnień Stephena Cleary'ego, które są bardziej ukierunkowane na kogoś, kto ma techniczne zrozumienie async/ awaitjuż.


Powiedzmy, czy wszystkie te zadania zwracają liczbę całkowitą, a jeśli używam wyniku pierwszego zadania w drugim zadaniu (lub w obliczeniach). Czy byłoby źle?
veerendra gupta

3
@veerendragupta tak. W takim przypadku świadomie zdecydowałbyś się nie uruchamiać ich asynchronicznie (ponieważ nie są one asynchroniczne). Jest także kilka innych rzeczy do zrozumienia dotyczących kontekstu konfiguracji, których nie będę tu wchodził
Joe Phillips

Czy await MethodCall()to absolutna strata? Równie dobrze możesz upuścić await/ async?
Vitani

2
@Jocie Niezupełnie. Kiedy dzwonisz await, myślę, że zwalnia wątek z powrotem do puli zamiast go trzymać. Dzięki temu jest dostępny do użycia w innym miejscu w oczekiwaniu na zwrot zadania
Joe Phillips

2
@JoePhillips Myślę, że to, co właśnie powiedziałeś, to esencja async / czekaj. Wątek wywołujący jest zwolniony i może być używany przez inne procesy na komputerze. Po zakończeniu połączenia oczekującego nowy wątek jest używany do wznowienia działania początkowego programu wywołującego. Dzwoniący wciąż czeka, ale korzyścią jest, że w międzyczasie zostanie zwolniony wątek. To jest korzyść z asynchronizacji / czekania?
Bob Horn

147

Oprócz innych odpowiedzi, spójrz na czekaj (C # Reference)

a dokładniej na dołączonym przykładzie, to trochę wyjaśnia twoją sytuację

Poniższy przykład formularzy Windows Forms ilustruje użycie funkcji Oczekuj w metodzie asynchronicznej WaitAsynchronouslyAsync. Porównaj zachowanie tej metody z zachowaniem WaitSynchronously. Bez operatora oczekującego zastosowanego do zadania, WaitSynchronicznie działa synchronicznie pomimo użycia modyfikatora asynchronicznego w definicji i wywołania Thread.Sleep w jego ciele.

private async void button1_Click(object sender, EventArgs e)
{
    // Call the method that runs asynchronously.
    string result = await WaitAsynchronouslyAsync();

    // Call the method that runs synchronously.
    //string result = await WaitSynchronously ();

    // Display the result.
    textBox1.Text += result;
}

// The following method runs asynchronously. The UI thread is not
// blocked during the delay. You can move or resize the Form1 window 
// while Task.Delay is running.
public async Task<string> WaitAsynchronouslyAsync()
{
    await Task.Delay(10000);
    return "Finished";
}

// The following method runs synchronously, despite the use of async.
// You cannot move or resize the Form1 window while Thread.Sleep
// is running because the UI thread is blocked.
public async Task<string> WaitSynchronously()
{
    // Add a using directive for System.Threading.
    Thread.Sleep(10000);
    return "Finished";
}

3
Dziękuję za odpowiedź. Ale czy WaitAsynchronouslyAsync () jest wykonywany w osobnym wątku?
Dan Dinu

32
Wierzę, więc z sekcji Oczekiwanie na wyrażenie nie blokuje wątku, w którym się wykonuje. Zamiast tego powoduje, że kompilator zarejestruje resztę metody asynchronicznej jako kontynuację oczekiwanego zadania. Kontrola następnie powraca do obiektu wywołującego metodę asynchroniczną. Po zakończeniu zadania wywołuje jego kontynuację, a wykonywanie metody asynchronicznej jest wznawiane od miejsca, w którym zostało przerwane.
Adriaan Stander

13
Zgodnie z tym artykułem MSDN „Słowa kluczowe asynchroniczne i oczekujące nie powodują tworzenia dodatkowych wątków ... metoda asynchroniczna nie działa na własnym wątku”. Rozumiem, że w oczekiwaniu na słowa kluczowe struktura przeskakuje do przodu (z powrotem do dzwoniącego), aby umożliwić uruchomienie wszystkich możliwych niezależnych kodów podczas oczekiwania na zakończenie długich operacji. Myślę, że oznacza to, że po uruchomieniu całego niezależnego kodu, jeśli długa operacja nie powróci, zostanie zablokowana. Jednak dopiero teraz się tego uczę.
Vimes,

9
@astander To jest nieprawidłowe. Robi nie wykonać na innym wątku. Jest to po prostu harmonogram kontynuacji (reszta metody), która ma być wywoływana, gdy timer używany przez Task.Delaypożary.
MgSam

1
Ta odpowiedź jest błędna z powodu snu. Zobacz zaakceptowaną odpowiedź, czekając na Task.Delay (1000); który ma prawidłowe zachowanie.
Jared Updike

62

Wyświetlanie powyższych wyjaśnień w działaniu w prostym programie konsoli:

class Program
{
    static void Main(string[] args)
    {
        TestAsyncAwaitMethods();
        Console.WriteLine("Press any key to exit...");
        Console.ReadLine();
    }

    public async static void TestAsyncAwaitMethods()
    {
        await LongRunningMethod();
    }

    public static async Task<int> LongRunningMethod()
    {
        Console.WriteLine("Starting Long Running method...");
        await Task.Delay(5000);
        Console.WriteLine("End Long Running method...");
        return 1;
    }
}

Wyjście to:

Starting Long Running method...
Press any key to exit...
End Long Running method...

A zatem,

  1. Main uruchamia metodę długotrwałego działania za pośrednictwem TestAsyncAwaitMethods. To natychmiast powraca bez zatrzymywania bieżącego wątku i natychmiast pojawia się komunikat „Naciśnij dowolny klawisz, aby wyjść”
  2. Wszystko to LongRunningMethoddziała w tle. Po zakończeniu inny wątek z Threadpool wychwytuje ten kontekst i wyświetla końcowy komunikat

Zatem nie wątek jest zablokowany.


„Wciśnij dowolny klawisz, aby wyjść ...” pojawi się w której części wyjścia?
StudioX

1
a jakie jest wykorzystanie (return 1)? czy to konieczne?
StudioX

1
@StudioX myślę, że musi mieć liczbę całkowitą typu zwracanego
Kuba Do

Myślę, że ta return 1część zasługuje na dodatkowe wyjaśnienie: awaitsłowo kluczowe pozwala bezpośrednio zwrócić podstawowy typ Task<T>, co ułatwia dostosowanie wychodzącego kodu do świata oczekujących / asynchronicznych . Ale nie musisz zwracać wartości, ponieważ możliwe jest zwrócenie wartości Taskbez określania zwracanego typu, który byłby odpowiednikiem voidmetody synchronicznej . Pamiętaj, że C # pozwala na async voidmetody, ale powinieneś tego unikać, chyba że zajmujesz się obsługą zdarzeń.
Christiano Kiss

41

Myślę, że wybrałeś zły przykład System.Threading.Thread.Sleep

Celem asynczadania jest umożliwienie mu wykonywania w tle bez blokowania głównego wątku, na przykład robieniaDownloadFileAsync

System.Threading.Thread.Sleep nie jest czymś, co „jest zrobione”, po prostu śpi, dlatego twoja kolejna linia jest osiągana po 5 sekundach ...

Przeczytaj ten artykuł, myślę, że jest to świetne wyjaśnienie asynci awaitkoncepcja: http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx


3
Dlaczego sen jest złym przykładem, ale pobieranie jest dobrym przykładem. To jest coś w rodzaju FooBar, kiedy widzę Thread. Sen. Rozumiem, że jest pewne zadanie, które wymaga czasu. Myślę, że jego pytanie jest istotne
Abdurrahim

1
@Abdurrahim Thread.Sleepblokuje wątek (wątek nie może zrobić nic innego, jak siedzieć bezczynnie), ale metoda asynchroniczna tego nie robi. W przypadku DownloadFileAsyncwątku można przejść i zrobić coś innego, dopóki odpowiedź nie zostanie wysłana ze zdalnego serwera. Lepszym symbolem zastępczym dla „jakiegoś zadania wymagającego czasu” w metodzie asynchronicznej jest to Task.Delay, że jest to tak naprawdę asynchroniczne.
Gabriel Luci,

@GabrielLuci mój sprzeciw nie dotyczy opóźnienia w czasie snu; Twoja odpowiedź przypomina bardziej słomianą odpowiedź; Jeśli umieścisz to jako komentarz do pytania, które nie byłoby niczym, mógłbym się temu sprzeciwić, ale jako odpowiedź bardziej śmierdzi słomkową odpowiedzią. Myślę, że nadal dobrze jest używać asynchronizacji, nawet jeśli wszystkie połączenia, które musi wykonywać, będą blokować połączenia; Nie unieważni to całego celu ... Nawet wszystko, co pozostanie, będzie cukrem syntaktycznym, który liczy się jako ważny przypadek,
Abdurrahim

1
To nie była moja odpowiedź. Ale aby zająć się twoim punktem: zależy to od celu metody. Jeśli tylko chciał zadzwonić z metody, to mu się udało. Ale w tym przypadku próbował stworzyć metodę, która działałaby asynchronicznie. Zrobił to za pomocą asyncsłowa kluczowego. Ale jego metoda wciąż działała synchronicznie, a ta odpowiedź doskonale wyjaśniała dlaczego: ponieważ tak naprawdę nie uruchomił żadnego kodu asynchronicznego. Oznaczone metody asyncnadal działają synchronicznie aż do momentu, gdy będziesz awaitniekompletny Task. Jeśli nie await, metoda działa synchronicznie, a kompilator ostrzeże Cię o tym.
Gabriel Luci,

23

Oto szybki program konsoli, aby wyjaśnić tym, którzy go śledzą. Ta TaskToDometoda jest długotrwałą metodą asynchroniczną. Uruchomienie asynchroniczne odbywa się TestAsyncmetodą Metoda pętli testowych po prostu uruchamia TaskToDozadania i uruchamia je asynchronicznie. Widać to w wynikach, ponieważ nie wykonują się one w tej samej kolejności od uruchomienia do uruchomienia - po zakończeniu zgłaszają się do wątku interfejsu użytkownika konsoli. Uproszczone, ale myślę, że uproszczone przykłady lepiej wydobywają rdzeń wzoru niż przykłady bardziej zaangażowane:

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

namespace TestingAsync
{
    class Program
    {
        static void Main(string[] args)
        {
            TestLoops();
            Console.Read();
        }

        private static async void TestLoops()
        {
            for (int i = 0; i < 100; i++)
            {
                await TestAsync(i);
            }
        }

        private static Task TestAsync(int i)
        {
            return Task.Run(() => TaskToDo(i));
        }

        private async static void TaskToDo(int i)
        {
            await Task.Delay(10);
            Console.WriteLine(i);
        }
    }
}

20

Dla najszybszej nauki ..

  • Zrozumienie przebiegu wykonywania metody (z diagramem): 3 minuty

  • Pytanie introspekcja (nauka): 1 min

  • Szybko przejdź przez składnię cukru: 5 minut

  • Podziel się zamieszaniem programisty: 5 minut

  • Problem: Szybka zmiana rzeczywistej implementacji normalnego kodu na kod asynchroniczny: 2 minuty

  • Gdzie teraz?

Zrozumienie przebiegu wykonywania metody (z diagramem): 3 minuty

Na tym obrazie skup się na # 6 (nic więcej) wprowadź opis zdjęcia tutaj

W kroku # 6: Wykonanie zatrzymało się tutaj, ponieważ zabrakło pracy. Aby kontynuować, potrzebuje wyniku z getStringTask (rodzaj funkcji). Dlatego używa awaitoperatora, aby zawiesić postęp i oddać kontrolę (wydajność) wzywającemu (tej metody jesteśmy). Rzeczywiste wywołanie getStringTask zostało wykonane wcześniej w punkcie 2. Na nr 2 złożono obietnicę zwrócenia wyniku łańcucha.Ale kiedy zwróci wynik? Czy powinniśmy (# 1: AccessTheWebAsync) ponownie wykonać drugie połączenie? Kto otrzyma wynik, # 2 (komunikat wywołujący) lub # 6 (czekający komunikat)

Zewnętrzny obiekt wywołujący AccessTheWebAsync () również czeka teraz. Więc osoba dzwoniąca czeka na AccessTheWebAsync, a AccessTheWebAsync w tej chwili czeka na GetStringAsync. Interesującą rzeczą jest AccessTheWebAsync wykonał trochę pracy przed czekaniem (# 4), być może, aby zaoszczędzić czas na czekaniu. Ta sama swoboda wielozadaniowości jest również dostępna dla zewnętrznego rozmówcy (i wszystkich dzwoniących w łańcuchu) i jest to największy plus tego „asynchronicznego” rozwiązania! Wydaje Ci się, że jest synchroniczny ... lub normalny, ale tak nie jest.

Pamiętaj, że metoda została już zwrócona (# 2), nie może powrócić ponownie (nie drugi raz). Więc skąd dzwoniący będzie wiedział? Chodzi przede wszystkim o zadania! Zadanie zostało przekazane. Zadanie było oczekiwane (nie metoda, nie wartość). Wartość zostanie ustawiona w zadaniu. Status zadania zostanie ustawiony na zakończenie. Dzwoniący monitoruje tylko Zadanie (# 6). Więc 6 # jest odpowiedzią na pytanie, gdzie / kto uzyska wynik. Dalsze informacje na później tutaj .

Pytanie introspekcja ze względu na naukę: 1 min

Dostosujmy nieco pytanie:

Jak i kiedy używać i ? asyncawait Tasks

Ponieważ nauka Taskautomatycznie obejmuje pozostałe dwa (i odpowiada na twoje pytanie)

Szybko przejdź przez składnię cukru: 5 minut

  • Przed konwersją (metoda oryginalna)

    internal static int Method(int arg0, int arg1) { int result = arg0 + arg1; IO(); // Do some long running IO. return result; }

  • Metoda zadaniowa do wywołania powyższej metody

    internal static Task<int> MethodTask(int arg0, int arg1) { Task<int> task = new Task<int>(() => Method(arg0, arg1)); task.Start(); // Hot task (started task) should always be returned. return task; }

Czy wspominaliśmy o oczekiwaniu lub asynchronizacji? Nie. Wywołaj powyższą metodę, a otrzymasz zadanie, które możesz monitorować. Wiesz już, co zwraca zadanie .. liczba całkowita.

  • Wywołanie zadania jest nieco trudne i wtedy zaczynają się pojawiać słowa kluczowe. Nazwijmy MethodTask ()

    internal static async Task<int> MethodAsync(int arg0, int arg1) { int result = await HelperMethods.MethodTask(arg0, arg1); return result; }

Ten sam kod powyżej dodany jako obraz poniżej: wprowadź opis zdjęcia tutaj

  1. „Czekamy” na zakończenie zadania. Stądawait
  2. Ponieważ używamy funkcji czekaj, musimy użyć async(obowiązkowa składnia)
  3. MethodAsync z Asyncprefiksem (standard kodowania)

awaitjest łatwy do zrozumienia, ale pozostałe dwa ( async, Async) mogą nie być :). Cóż, należy zrobić dużo więcej sensu kompilatora though.Further czyta na później tutaj

Więc są 2 części.

  1. Utwórz „Zadanie”
  2. Utwórz cukier składniowy, aby wywołać zadanie ( await+async)

Pamiętaj, że mieliśmy zewnętrzny program wywołujący AccessTheWebAsync () i ten program wywołujący również nie jest oszczędzony ... tzn. Potrzebuje tego samego await+async. I łańcuch trwa. Ale zawsze będzie Taskjeden koniec.

Wszystko w porządku, ale jeden programista był zaskoczony, widząc brak # 1 (Zadanie) ...

Podziel się zamieszaniem programisty: 5 minut

Deweloper popełnił błąd, nie wdrażając, Taskale nadal działa! Spróbuj zrozumieć pytanie i tylko zaakceptowaną odpowiedź podaną tutaj . Mam nadzieję, że przeczytałeś i w pełni zrozumiałeś. Podsumowując, możemy nie zobaczyć / zaimplementować „Zadania”, ale jest ono zaimplementowane gdzieś w klasie nadrzędnej. Podobnie w naszym przykładzie wywołanie już zbudowanej MethodAsync()jest znacznie łatwiejsze niż implementacja tej metody za pomocą Task( MethodTask()) samego siebie. Większość programistów ma problemy z przekonaniem się Taskspodczas konwersji kodu na kod asynchroniczny.

Wskazówka: spróbuj znaleźć istniejącą implementację Async (jak MethodAsynclub ToListAsync), aby outsourcingować trudność. Musimy więc tylko radzić sobie z Async i czekać (co jest łatwe i bardzo podobne do normalnego kodu)

Problem: Szybka zmiana rzeczywistej implementacji normalnego kodu na działanie asynchroniczne: 2 minuty

Linia kodu pokazana poniżej w Warstwie danych zaczęła się łamać (wiele miejsc). Ponieważ zaktualizowaliśmy część naszego kodu z .Net Framework 4.2. * Do .Net core. Musieliśmy to naprawić w ciągu 1 godziny w całej aplikacji!

var myContract = query.Where(c => c.ContractID == _contractID).First();

bułka z masłem!

  1. Zainstalowaliśmy pakiet nugetowy EntityFramework, ponieważ ma on QueryableExtensions. Albo innymi słowy to nie realizacja asynchroniczny (zadanie), więc może przetrwać z prostymi Asynci awaitw kodzie.
  2. namespace = Microsoft.EntityFrameworkCore

wywołanie linii kodu zostało zmienione w ten sposób

var myContract = await query.Where(c => c.ContractID == _contractID).FirstAsync();
  1. Zmieniono podpis metody z

    Contract GetContract(int contractnumber)

    do

    async Task<Contract> GetContractAsync(int contractnumber)

  2. wpływ GetContractAsync(123456);miała również metoda wywoływania: została wywołana jakoGetContractAsync(123456).Result;

  3. Zmienialiśmy to wszędzie w 30 minut!

Ale architekt powiedział nam, aby nie używać biblioteki EntityFramework tylko do tego! ups! dramat! Następnie stworzyliśmy niestandardową implementację Zadania (Fuj). Które wiesz jak. Wciąż łatwe! .. nadal fuj ..

Gdzie teraz? Jest wspaniały, szybki film, który moglibyśmy obejrzeć o Konwertowaniu wywołań synchronicznych na asynchroniczne w ASP.Net Core , być może jest to kierunek, w którym można pójść po przeczytaniu tego.


fantastyczna odpowiedź! pomogło mi to ton
cklimowski

1
Niezła odpowiedź. Możesz po prostu chcieć naprawić kilka drobnych rzeczy, takich jak: (a) wzmianka o „.Net Framework 4.2” (nie ma takiej znanej mi wersji, istnieje) (b) obudowa w EntityFrameWork => EntityFramework
immitev

15

Wszystkie odpowiedzi tutaj Task.Delay()lub inne wbudowane asyncfunkcje. Ale oto mój przykład, który nie korzysta z żadnej z tych asyncfunkcji:

// Starts counting to a large number and then immediately displays message "I'm counting...". 
// Then it waits for task to finish and displays "finished, press any key".
static void asyncTest ()
{
    Console.WriteLine("Started asyncTest()");
    Task<long> task = asyncTest_count();
    Console.WriteLine("Started counting, please wait...");
    task.Wait(); // if you comment this line you will see that message "Finished counting" will be displayed before we actually finished counting.
    //Console.WriteLine("Finished counting to " + task.Result.ToString()); // using task.Result seems to also call task.Wait().
    Console.WriteLine("Finished counting.");
    Console.WriteLine("Press any key to exit program.");
    Console.ReadLine();
}

static async Task<long> asyncTest_count()
{
    long k = 0;
    Console.WriteLine("Started asyncTest_count()");
    await Task.Run(() =>
    {
        long countTo = 100000000;
        int prevPercentDone = -1;
        for (long i = 0; i <= countTo; i++)
        {
            int percentDone = (int)(100 * (i / (double)countTo));
            if (percentDone != prevPercentDone)
            {
                prevPercentDone = percentDone;
                Console.Write(percentDone.ToString() + "% ");
            }

            k = i;
        }
    });
    Console.WriteLine("");
    Console.WriteLine("Finished asyncTest_count()");
    return k;
}

2
Dziękuję Ci! pierwsza odpowiedź, która faktycznie działa, zamiast czekać.
Jeffnl

dziękuję za pokaz task.Wait();i jak można go użyć, aby uniknąć asynchronizacji / czekać na piekło: P
enkoder

12

Ta odpowiedź ma na celu dostarczenie pewnych informacji specyficznych dla ASP.NET.

Korzystając z asynchronizacji / oczekiwania na kontroler MVC, można zwiększyć wykorzystanie puli wątków i osiągnąć znacznie lepszą przepustowość, jak wyjaśniono w poniższym artykule,

http://www.asp.net/mvc/tutorials/mvc-4/using-asynchronous-methods-in-aspnet-mvc-4

W aplikacjach internetowych, które widzą dużą liczbę równoczesnych żądań podczas uruchamiania lub obciążają się serią (gdzie nagle wzrasta współbieżność), wywołanie tych usług internetowych asynchronicznie zwiększy czas reakcji aplikacji. Przetwarzanie żądania asynchronicznego zajmuje tyle samo czasu, co żądanie synchroniczne. Na przykład, jeśli żądanie wykonuje wywołanie usługi internetowej, którego wypełnienie wymaga dwóch sekund, żądanie trwa dwie sekundy, niezależnie od tego, czy jest wykonywane synchronicznie, czy asynchronicznie. Jednak podczas wywołania asynchronicznego wątek nie jest blokowany przed odpowiadaniem na inne żądania podczas oczekiwania na zakończenie pierwszego żądania. Dlatego żądania asynchroniczne zapobiegają kolejkowaniu żądań i zwiększaniu puli wątków, gdy istnieje wiele równoczesnych żądań wywołujących długotrwałe operacje.


12

Asynchronizuj i czekaj na proste objaśnienie

Prosta analogia

Osoba może poczekać na poranny pociąg. To wszystko, co robią, ponieważ jest to ich główne zadanie, które obecnie wykonują. (programowanie synchroniczne (co zwykle robisz!))

Inna osoba może poczekać na poranny pociąg, podczas gdy pali papierosa, a następnie pije kawę. (Programowanie asynchroniczne)

Co to jest programowanie asynchroniczne?

Programowanie asynchroniczne polega na tym, że programista zdecyduje się uruchomić część swojego kodu w innym wątku niż główny wątek wykonania, a następnie powiadomi główny wątek o zakończeniu.

Co faktycznie robi słowo kluczowe asynchroniczne?

Prefiks słowa kluczowego async w nazwie metody takiej jak

async void DoSomething(){ . . .

pozwala programiście używać słowa kluczowego „czekaj” podczas wywoływania zadań asynchronicznych. To wszystko, co robi.

Dlaczego to jest ważne?

W wielu systemach oprogramowania główny wątek jest zarezerwowany dla operacji związanych konkretnie z interfejsem użytkownika. Jeśli korzystam z bardzo złożonego algorytmu rekurencyjnego, którego wykonanie zajmuje 5 sekund na moim komputerze, ale uruchamiam go w głównym wątku (wątek interfejsu użytkownika). Gdy użytkownik spróbuje kliknąć dowolny element mojej aplikacji, będzie wyglądał na zawieszony ponieważ mój główny wątek ustawił się w kolejce i przetwarza obecnie zbyt wiele operacji. W rezultacie główny wątek nie może przetworzyć kliknięcia myszy, aby uruchomić metodę z kliknięcia przycisku.

Kiedy używasz Async i Oczekujesz?

Używaj asynchronicznych słów kluczowych idealnie, gdy robisz coś, co nie wymaga interfejsu użytkownika.

Powiedzmy, że piszesz program, który pozwala użytkownikowi na szkicowanie na telefonie komórkowym, ale co 5 sekund będzie sprawdzał pogodę w Internecie.

Powinniśmy czekać na połączenie z pollingiem co 5 sekund do sieci, aby uzyskać pogodę, ponieważ użytkownik aplikacji musi kontynuować interakcję z mobilnym ekranem dotykowym, aby rysować ładne zdjęcia.

Jak korzystać z Async i Oczekuj

Kontynuując powyższy przykład, oto pseudo kod, jak go napisać:

    //ASYNCHRONOUS
    //this is called using the await keyword every 5 seconds from a polling timer or something.

    async Task CheckWeather()
    {
        var weather = await GetWeather();
        //do something with the weather now you have it
    }

    async Task<WeatherResult> GetWeather()
    {

        var weatherJson = await CallToNetworkAddressToGetWeather();
        return deserializeJson<weatherJson>(weatherJson);
    }

    //SYNCHRONOUS
    //This method is called whenever the screen is pressed
    void ScreenPressed()
    {
        DrawSketchOnScreen();
    }

Dodatkowe uwagi - aktualizacja

Zapomniałem wspomnieć w moich oryginalnych notatkach, że w języku C # możesz oczekiwać tylko metod zapakowanych w Zadania. na przykład możesz poczekać na tę metodę:

// awaiting this will return a string.
// calling this without await (synchronously) will result in a Task<string> object.
async Task<string> FetchHelloWorld() {..

Nie możesz oczekiwać metod, które nie są zadaniami takimi jak to:

async string FetchHelloWorld() {..

Tutaj możesz przejrzeć kod źródłowy klasy Task .


4
Dziękujemy za poświęcenie czasu na napisanie tego.
Prashant

10

Async / Await

W rzeczywistości Async / Await to para słów kluczowych, które są po prostu cukrem syntaktycznym do tworzenia wywołania zwrotnego zadania asynchronicznego.

Weź przykład z tej operacji:

    public static void DoSomeWork()
    {
        var task = Task.Run(() =>
        {
            // [RUNS ON WORKER THREAD]

            // IS NOT bubbling up due to the different threads
            throw new Exception();
            Thread.Sleep(2000);

            return "Hello";
        });

        // This is the callback
        task.ContinueWith((t) => {
            // -> Exception is swallowed silently
            Console.WriteLine("Completed");

            // [RUNS ON WORKER THREAD]
        });
    }

Powyższy kod ma kilka wad. Błędy nie są przekazywane i trudno je odczytać. Ale Async i Await przychodzą, aby nam pomóc:

    public async static void DoSomeWork()
    {
        var result = await Task.Run(() =>
        {
            // [RUNS ON WORKER THREAD]

            // IS bubbling up
            throw new Exception();
            Thread.Sleep(2000);

            return "Hello";
        });

        // every thing below is a callback 
        // (including the calling methods)

        Console.WriteLine("Completed");

    }

Oczekiwania na wywołania muszą odbywać się w metodach asynchronicznych. Ma to pewne zalety:

  • Zwraca wynik zadania
  • automatycznie tworzy połączenie zwrotne
  • sprawdza, czy nie ma błędów i pozwala im na bąbelkowanie w callstacku (tylko do oczekujących połączeń w callstacku)
  • czeka na wynik
  • uwalnia główny wątek
  • uruchamia wywołanie zwrotne w głównym wątku
  • używa do zadania wątku roboczego z puli wątków
  • sprawia, że ​​kod jest łatwy do odczytania
  • i dużo więcej

UWAGA : Async i Await są używane z wywołaniami asynchronicznymi, aby ich nie wykonywać. W tym celu musisz użyć Task Libary , takiego jak Task.Run ().

Oto porównanie rozwiązań oczekujących i oczekujących

Oto rozwiązanie asynchroniczne:

    public static long DoTask()
    {
        stopWatch.Reset();
        stopWatch.Start();

        // [RUNS ON MAIN THREAD]
        var task = Task.Run(() => {
            Thread.Sleep(2000);
            // [RUNS ON WORKER THREAD]
        });
        // goes directly further
        // WITHOUT waiting until the task is finished

        // [RUNS ON MAIN THREAD]

        stopWatch.Stop();
        // 50 milliseconds
        return stopWatch.ElapsedMilliseconds;
    }

To jest metoda asynchroniczna:

    public async static Task<long> DoAwaitTask()
    {
        stopWatch.Reset();
        stopWatch.Start();

        // [RUNS ON MAIN THREAD]

        await Task.Run(() => {
            Thread.Sleep(2000);
            // [RUNS ON WORKER THREAD]
        });
        // Waits until task is finished

        // [RUNS ON MAIN THREAD]

        stopWatch.Stop();
        // 2050 milliseconds
        return stopWatch.ElapsedMilliseconds;
    }

Możesz wywołać metodę asynchroniczną bez słowa kluczowego Oczekiwanie, ale oznacza to, że wszelkie wyjątki tutaj są połykane w trybie zwolnienia:

    public static Stopwatch stopWatch { get; } = new Stopwatch();

    static void Main(string[] args)
    {
        Console.WriteLine("DoAwaitTask: " + DoAwaitTask().Result + " ms");
        // 2050 (2000 more because of the await)
        Console.WriteLine("DoTask: " + DoTask() + " ms");
        // 50
        Console.ReadKey();
    }

Async i Await nie są przeznaczone do obliczeń równoległych. Służą do nie blokowania głównego wątku. Jeśli chodzi o aplikacje asp.net lub Windows, blokowanie głównego wątku z powodu połączenia sieciowego jest złą rzeczą. Jeśli to zrobisz, aplikacja przestanie odpowiadać, a nawet ulegnie awarii.

Sprawdź MS Docs, aby uzyskać więcej przykładów.


9

Szczerze mówiąc, nadal uważam, że najlepszym wytłumaczeniem jest przyszłość i obietnice na Wikipedii: http://en.wikipedia.org/wiki/Futures_and_promises

Podstawową ideą jest to, że masz osobną pulę wątków, które wykonują zadania asynchronicznie. Podczas korzystania z niego. Obiekt jednak obiecuje, że wykona operację w pewnym momencie i da ci wynik, gdy o to poprosisz. Oznacza to, że blokuje się, gdy zażądasz wyniku i nie został on zakończony, ale w przeciwnym razie zostanie wykonany w puli wątków.

Stamtąd możesz zoptymalizować rzeczy: niektóre operacje można zaimplementować asynchronicznie, a także zoptymalizować takie rzeczy, jak operacje we / wy pliku i komunikacja sieciowa, grupując kolejne żądania i / lub zmieniając ich kolejność. Nie jestem pewien, czy jest to już w ramach zadań Microsoftu - ale jeśli tak nie jest, byłoby to jedną z pierwszych rzeczy, które chciałbym dodać.

Możesz faktycznie zaimplementować przyszłe sortowanie wzorów z wydajnością w C # 4.0. Jeśli chcesz wiedzieć, jak to dokładnie działa, mogę polecić ten link, który wykonuje przyzwoitą robotę: http://code.google.com/p/fracture/source/browse/trunk/Squared/TaskLib/ . Jeśli jednak zaczniesz się z tym bawić, zauważysz, że naprawdę potrzebujesz pomocy językowej, jeśli chcesz robić wszystkie fajne rzeczy - dokładnie to zrobiła Microsoft.


8

Zobacz ten skrzypce https://dotnetfiddle.net/VhZdLU (i popraw go, jeśli to możliwe), aby uruchomić prostą aplikację konsolową, która pokazuje użycie Task, Task.WaitAll (), asynchronizuje i oczekuje na operatorów w tym samym programie.

To skrzypce powinno wyczyścić koncepcję cyklu realizacji.

Oto przykładowy kod

using System;
using System.Threading.Tasks;

public class Program
{
    public static void Main()
    {               
        var a = MyMethodAsync(); //Task started for Execution and immediately goes to Line 19 of the code. Cursor will come back as soon as await operator is met       
        Console.WriteLine("Cursor Moved to Next Line Without Waiting for MyMethodAsync() completion");
        Console.WriteLine("Now Waiting for Task to be Finished");       
        Task.WaitAll(a); //Now Waiting      
        Console.WriteLine("Exiting CommandLine");       
    }

    public static async Task MyMethodAsync()
    {
        Task<int> longRunningTask = LongRunningOperation();
        // independent work which doesn't need the result of LongRunningOperationAsync can be done here
        Console.WriteLine("Independent Works of now executes in MyMethodAsync()");
        //and now we call await on the task 
        int result = await longRunningTask;
        //use the result 
        Console.WriteLine("Result of LongRunningOperation() is " + result);
    }

    public static async Task<int> LongRunningOperation() // assume we return an int from this long running operation 
    {
        Console.WriteLine("LongRunningOperation() Started");
        await Task.Delay(2000); // 2 second delay
        Console.WriteLine("LongRunningOperation() Finished after 2 Seconds");
        return 1;
    }   

}

Śledzenie pochodzące z okna wyjściowego: wprowadź opis zdjęcia tutaj


3
public static void Main(string[] args)
{
    string result = DownloadContentAsync().Result;
    Console.ReadKey();
}

// You use the async keyword to mark a method for asynchronous operations.
// The "async" modifier simply starts synchronously the current thread. 
// What it does is enable the method to be split into multiple pieces.
// The boundaries of these pieces are marked with the await keyword.
public static async Task<string> DownloadContentAsync()// By convention, the method name ends with "Async
{
    using (HttpClient client = new HttpClient())
    {
        // When you use the await keyword, the compiler generates the code that checks if the asynchronous operation is finished.
        // If it is already finished, the method continues to run synchronously.
        // If not completed, the state machine will connect a continuation method that must be executed WHEN the Task is completed.


        // Http request example. 
        // (In this example I can set the milliseconds after "sleep=")
        String result = await client.GetStringAsync("http://httpstat.us/200?sleep=1000");

        Console.WriteLine(result);

        // After completing the result response, the state machine will continue to synchronously execute the other processes.


        return result;
    }
}

3

Na wyższym poziomie:

1) Słowo kluczowe Async umożliwia oczekiwanie i to wszystko, co robi. Słowo kluczowe asynchroniczne nie uruchamia metody w osobnym wątku. Początkowa metoda asynchroniczna działa synchronicznie, dopóki nie trafi w oczekiwanie na czasochłonne zadanie.

2) Możesz poczekać na metodę zwracającą Zadanie lub Zadanie typu T. Nie możesz czekać na metodę async void.

3) W chwili, gdy główny wątek napotka na czasochłonne zadanie lub gdy rozpocznie się faktyczna praca, główny wątek powraca do programu wywołującego bieżącą metodę.

4) Jeśli główny wątek oczekuje na zadanie, które jest nadal wykonywane, nie czeka na nie i wraca do programu wywołującego bieżącą metodę. W ten sposób aplikacja pozostaje responsywna.

5) Oczekiwanie na zadanie przetwarzania, będzie teraz wykonywane w innym wątku niż pula wątków.

6) Po zakończeniu tego zadania oczekującego cały kod poniżej zostanie wykonany przez osobny wątek

Poniżej znajduje się przykładowy kod. Wykonaj go i sprawdź identyfikator wątku

using System;
using System.Threading;
using System.Threading.Tasks;

namespace AsyncAwaitDemo
{
    class Program
    {
        public static async void AsynchronousOperation()
        {
            Console.WriteLine("Inside AsynchronousOperation Before AsyncMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
            //Task<int> _task = AsyncMethod();
            int count = await AsyncMethod();

            Console.WriteLine("Inside AsynchronousOperation After AsyncMethod Before Await, Thread Id: " + Thread.CurrentThread.ManagedThreadId);

            //int count = await _task;

            Console.WriteLine("Inside AsynchronousOperation After AsyncMethod After Await Before DependentMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);

            DependentMethod(count);

            Console.WriteLine("Inside AsynchronousOperation After AsyncMethod After Await After DependentMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
        }

        public static async Task<int> AsyncMethod()
        {
            Console.WriteLine("Inside AsyncMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
            int count = 0;

            await Task.Run(() =>
            {
                Console.WriteLine("Executing a long running task which takes 10 seconds to complete, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(20000);
                count = 10;
            });

            Console.WriteLine("Completed AsyncMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);

            return count;
        }       

        public static void DependentMethod(int count)
        {
            Console.WriteLine("Inside DependentMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId + ". Total count is " + count);
        }

        static void Main(string[] args)
        {
            Console.WriteLine("Started Main method, Thread Id: " + Thread.CurrentThread.ManagedThreadId);

            AsynchronousOperation();

            Console.WriteLine("Completed Main method, Thread Id: " + Thread.CurrentThread.ManagedThreadId);

            Console.ReadKey();
        }

    }
}

2

Tak jak ja to rozumiem też, nie powinno być trzeci termin dodaje się do mieszanki: Task.

Async to tylko kwalifikator, który umieścisz w metodzie, aby powiedzieć, że jest to metoda asynchroniczna.

Taskjest zwrotem asyncfunkcji. Wykonuje się asynchronicznie.

Jesteś awaitzadaniem. Gdy wykonanie kodu dojdzie do tej linii, sterowanie przeskakuje z powrotem do osoby dzwoniącej z otaczającej oryginalnej funkcji.

Jeśli zamiast tego przypiszesz powrót asyncfunkcji (tj. Task) Do zmiennej, gdy wykonanie kodu dojdzie do tego wiersza, to po prostu przechodzi poza ten wiersz w otaczającej funkcji, podczas gdy Taskwykonuje się asynchronicznie.


1

czy używanie ich jest równoznaczne z spawnowaniem wątków w tle w celu wykonania logiki o długim czasie trwania?

W tym artykule MDSN: Programowanie asynchroniczne z asynchronią i oczekiwaniem (C #) wyraźnie to wyjaśnia:

Słowa kluczowe asynchroniczne i oczekujące nie powodują utworzenia dodatkowych wątków. Metody asynchroniczne nie wymagają wielowątkowości, ponieważ metoda asynchroniczna nie działa na własnym wątku. Metoda działa w bieżącym kontekście synchronizacji i wykorzystuje czas w wątku tylko wtedy, gdy metoda jest aktywna.


1

W poniższym kodzie metoda HttpClient GetByteArrayAsync zwraca zadanie getContentsTask. Zadanie jest obietnicą utworzenia rzeczywistej tablicy bajtów po zakończeniu zadania. Operator oczekujący jest stosowany do getContentsTask w celu zawieszenia wykonania w SumPageSizesAsync do momentu ukończenia getContentsTask. W międzyczasie kontrola jest zwracana do programu wywołującego SumPageSizesAsync. Po zakończeniu getContentsTask wyrażenie oczekujące przekształca się w tablicę bajtów.

private async Task SumPageSizesAsync()
{
    // To use the HttpClient type in desktop apps, you must include a using directive and add a 
    // reference for the System.Net.Http namespace.
    HttpClient client = new HttpClient();
    // . . .
    Task<byte[]> getContentsTask = client.GetByteArrayAsync(url);
    byte[] urlContents = await getContentsTask;

    // Equivalently, now that you see how it works, you can write the same thing in a single line.
    //byte[] urlContents = await client.GetByteArrayAsync(url);
    // . . .
}

1

Poniżej znajduje się kod, który czyta plik programu Excel przez otwarcie okna dialogowego, a następnie używa asynchronizacji i czeka na uruchomienie asynchronicznego kodu, który odczytuje jeden po drugim wiersz z programu Excel i łączy się z siatką

namespace EmailBillingRates
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            lblProcessing.Text = "";
        }

        private async void btnReadExcel_Click(object sender, EventArgs e)
        {
            string filename = OpenFileDialog();

            Microsoft.Office.Interop.Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application();
            Microsoft.Office.Interop.Excel.Workbook xlWorkbook = xlApp.Workbooks.Open(filename);
            Microsoft.Office.Interop.Excel._Worksheet xlWorksheet = xlWorkbook.Sheets[1];
            Microsoft.Office.Interop.Excel.Range xlRange = xlWorksheet.UsedRange;
            try
            {
                Task<int> longRunningTask = BindGrid(xlRange);
                int result = await longRunningTask;

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message.ToString());
            }
            finally
            {
                //cleanup  
               // GC.Collect();
                //GC.WaitForPendingFinalizers();

                //rule of thumb for releasing com objects:  
                //  never use two dots, all COM objects must be referenced and released individually  
                //  ex: [somthing].[something].[something] is bad  

                //release com objects to fully kill excel process from running in the background  
                Marshal.ReleaseComObject(xlRange);
                Marshal.ReleaseComObject(xlWorksheet);

                //close and release  
                xlWorkbook.Close();
                Marshal.ReleaseComObject(xlWorkbook);

                //quit and release  
                xlApp.Quit();
                Marshal.ReleaseComObject(xlApp);
            }

        }

        private void btnSendEmail_Click(object sender, EventArgs e)
        {

        }

        private string OpenFileDialog()
        {
            string filename = "";
            OpenFileDialog fdlg = new OpenFileDialog();
            fdlg.Title = "Excel File Dialog";
            fdlg.InitialDirectory = @"c:\";
            fdlg.Filter = "All files (*.*)|*.*|All files (*.*)|*.*";
            fdlg.FilterIndex = 2;
            fdlg.RestoreDirectory = true;
            if (fdlg.ShowDialog() == DialogResult.OK)
            {
                filename = fdlg.FileName;
            }
            return filename;
        }

        private async Task<int> BindGrid(Microsoft.Office.Interop.Excel.Range xlRange)
        {
            lblProcessing.Text = "Processing File.. Please wait";
            int rowCount = xlRange.Rows.Count;
            int colCount = xlRange.Columns.Count;

            // dt.Column = colCount;  
            dataGridView1.ColumnCount = colCount;
            dataGridView1.RowCount = rowCount;

            for (int i = 1; i <= rowCount; i++)
            {
                for (int j = 1; j <= colCount; j++)
                {
                    //write the value to the Grid  
                    if (xlRange.Cells[i, j] != null && xlRange.Cells[i, j].Value2 != null)
                    {
                         await Task.Delay(1);
                         dataGridView1.Rows[i - 1].Cells[j - 1].Value =  xlRange.Cells[i, j].Value2.ToString();
                    }

                }
            }
            lblProcessing.Text = "";
            return 0;
        }
    }

    internal class async
    {
    }
}

0

Odpowiedzi tutaj są przydatne jako ogólne wskazówki dotyczące oczekiwania / asynchronizacji. Zawierają również pewne szczegóły dotyczące tego, jak połączenie oczekujące / asynchroniczne jest podłączone. Chciałbym podzielić się z Tobą praktycznymi doświadczeniami, które powinieneś wiedzieć przed użyciem tego wzoru.

Termin „czekaj” jest dosłowny, więc jakikolwiek wątek, który go wywołasz, poczeka na wynik metody przed kontynuowaniem. W wątku pierwszego planu jest to katastrofa . Wątek na pierwszym planie niesie ciężar tworzenia aplikacji, w tym widoków, modeli widoków, początkowych animacji i wszystkiego innego, co masz związane z tymi elementami. Więc kiedy czekasz na wątek pierwszego planu, zatrzymujesz aplikację. Użytkownik czeka i czeka, aż nic się nie wydarzy. Zapewnia to negatywne wrażenia użytkownika.

Z pewnością możesz poczekać na wątek w tle, używając różnych środków:

Device.BeginInvokeOnMainThread(async () => { await AnyAwaitableMethod(); });

// Notice that we do not await the following call, 
// as that would tie it to the foreground thread.
try
{
Task.Run(async () => { await AnyAwaitableMethod(); });
}
catch
{}

Pełny kod tych uwag znajduje się na stronie https://github.com/marcusts/xamarin-forms-annoyances . Zobacz rozwiązanie o nazwie AwaitAsyncAntipattern.sln.

Witryna GitHub zawiera również łącza do bardziej szczegółowej dyskusji na ten temat.


1
Z tego, co rozumiem, async / awaitto cukier składniowy do wywołań zwrotnych, nie ma on nic wspólnego z wątkami. msdn.microsoft.com/en-us/magazine/hh456401.aspx Dotyczy kodu niezwiązanego z procesorem, np. oczekiwania na wejście lub opóźnienia. Task.Runpowinien być używany tylko w przypadku kodu związanego z procesorem blog.stephencleary.com/2013/10/…
geometrikal

The term "await" is literal, so whatever thread you call it on will wait for the result of the method before continuing.To nieprawda - może miałeś na myśli Task.Wait ()? Gdy używasz await, ustawia resztę metody jako kontynuację, która zostanie wykonana po zakończeniu tego, czego oczekujesz. Wychodzi z metody, w której go użyłeś, aby osoba dzwoniąca mogła kontynuować. Następnie, gdy oczekiwana linia jest rzeczywiście kompletna, kończy resztę tej metody w jakimś wątku (zwykle wątku roboczym).
Don Cheadle

@geometrikal w samym rdzeniu async/awaitdotyczy uwolnienia wątków .NET. Gdy awaitoperacja jest naprawdę asynchroniczna (na przykład File.WriteAsync .NET.), Zawiesza pozostałą część użytej metody await, aby program wywołujący mógł kontynuować i potencjalnie zakończyć swój cel. Brak wątków blokujących lub oczekujących na awaitoperację -ed. Po zakończeniu edytowanej operacji awaitpozostała część async/awaitmetody jest umieszczana w wątku i wykonywana (podobnie jak pomysł wywołania zwrotnego).
Don Cheadle
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.