Co robi nowa funkcja C # await? [Zamknięte]

83

Czy ktoś może wyjaśnić, co awaitrobi ta funkcja?

Chris Nicol
źródło
1
Czy to jest to, o czym mówisz? Programowanie asynchroniczne w C # 5.0 część druga: Na co czekasz?
jordanbtucker
1
Zobacz także Asynchronous Programming with Async and Await
Robert Harvey
Ładne przykłady również na dotnetperls.com/async .
Miljen Mikic
Nie uważam, że to pytanie jest zbyt szerokie lub powinno zostać zamknięte. Pyta, co oznacza jedno słowo kluczowe. (Czy wcześniejsza wersja była w jakiś sposób inna?)
Panzercrisis

Odpowiedzi:

62

Właśnie o tym rozmawiali wczoraj w PDC !

Await jest używany w połączeniu z Tasks (programowanie równoległe) w .NET. To słowo kluczowe wprowadzane w następnej wersji .NET. Pozwala mniej więcej „wstrzymać” wykonywanie metody, aby poczekać na zakończenie wykonania zadania. Oto krótki przykład:

//create and run a new task  
Task<DataTable> dataTask = new Task<DataTable>(SomeCrazyDatabaseOperation);

//run some other code immediately after this task is started and running  
ShowLoaderControl();  
StartStoryboard();

//this will actually "pause" the code execution until the task completes.  It doesn't lock the thread, but rather waits for the result, similar to an async callback  
// please so also note, that the task needs to be started before it can be awaited. Otherwise it will never return
dataTask.Start();
DataTable table = await dataTask;

//Now we can perform operations on the Task result, as if we're executing code after the async operation completed  
listBoxControl.DataContext = table;  
StopStoryboard();  
HideLoaderControl();
RTigger
źródło
2
Kiedy jest to forma obietnic w języku C #: en.wikipedia.org/wiki/Futures_and_promises
12
Brzmi bardzo podobnie do Thread.Join ().
Steve Guidi
10
Przypomina mi COMEFROM
Joel Spolsky
20
Ze względu na kompletność dodajmy, że powyższy fragment kodu musi być opakowany w metodę, która jest ozdobiona słowem kluczowym async. Ta metoda zwróci się natychmiast po napotkaniu pierwszego słowa kluczowego await.
Przemek
14
Mówiąc słowami: umożliwia „wstrzymanie” metody, ale należy zauważyć, że nie wstrzymuje ani nie blokuje wątku.
Matt Crinklaw-Vogt,
47

Zasadniczo słowa kluczowe asynci awaitpozwalają określić, że wykonanie metody powinno zatrzymać się przy każdym użyciu await, które oznacza wywołania metod asynchronicznych, a następnie wznowić po zakończeniu operacji asynchronicznej. Pozwala to na wywołanie metody w głównym wątku aplikacji i asynchroniczną obsługę złożonej pracy, bez konieczności jawnego definiowania wątków i sprzężeń lub blokowania głównego wątku aplikacji.

Potraktuj to jako nieco podobne do yield returninstrukcji w metodzie tworzącej IEnumerable. Kiedy środowisko wykonawcze osiągnie wartość yield, w zasadzie zapisze bieżący stan metody i zwróci otrzymaną wartość lub odniesienie. Następnym razem, gdy IEnumerator.MoveNext () zostanie wywołana na zwracanym obiekcie (który jest generowany wewnętrznie przez środowisko uruchomieniowe), stary stan metody jest przywracany na stos, a wykonywanie jest kontynuowane z następnym wierszem po yield returnznaku, tak jakbyśmy nigdy nie opuszczali metoda. Bez tego słowa kluczowego typ IEnumerator musi być niestandardowo zdefiniowany, aby przechowywać stan i obsługiwać żądania iteracji, przy użyciu metod, które mogą stać się BARDZO złożone.

Podobnie metoda oznaczona jako asyncmusi mieć co najmniej jeden await. Na zasadzie await, że środowisko wykonawcze uratuje państwowej i stosu wywołań bieżącego wątku, uczynić asynchroniczne wywołanie, i relaks z powrotem do środowiska wykonawczego w pętli komunikatów do obsługi następnej wiadomości i utrzymania aplikacji elastyczne. Po zakończeniu operacji asynchronicznej przy następnej okazji planowania stos wywołań do operacji asynchronicznej jest z powrotem wypychany i kontynuowany tak, jakby wywołanie było synchroniczne.

Tak więc te dwa nowe słowa kluczowe w zasadzie upraszczają kodowanie procesów asynchronicznych, podobnie jak yield returnupraszczają generowanie niestandardowych wyliczalnych. Dzięki kilku słowom kluczowym i odrobinie wiedzy podstawowej możesz pominąć wszystkie zagmatwane i często podatne na błędy szczegóły tradycyjnego wzorca asynchronicznego. Będzie to NIEZNACZNE w prawie każdej aplikacji GUI sterowanej zdarzeniami, takiej jak Winforms, WPF lub Silverlight.

KeithS
źródło
31

Obecnie akceptowana odpowiedź jest myląca. awaitniczego nie wstrzymuje. Przede wszystkim może być używany tylko w metodach lub lambdach oznaczonych jako asynci zwracających Tasklub voidjeśli nie zależy ci na tym, aby Taskinstancja działała w tej metodzie.

Oto ilustracja:

internal class Program
{
    private static void Main(string[] args)
    {
        var task = DoWork();
        Console.WriteLine("Task status: " + task.Status);
        Console.WriteLine("Waiting for ENTER");
        Console.ReadLine();
    }

    private static async Task DoWork()
    {
        Console.WriteLine("Entered DoWork(). Sleeping 3");
        // imitating time consuming code
        // in a real-world app this should be inside task, 
        // so method returns fast
        Thread.Sleep(3000);

        await Task.Run(() =>
            {
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine("async task iteration " + i);
                    // imitating time consuming code
                    Thread.Sleep(1000);
                }
            });

        Console.WriteLine("Exiting DoWork()");
    }
}

Wynik:

Wprowadzono DoWork (). Uśpienie 3
iteracja zadania asynchronicznego 0
Status zadania: WaitingForActivation
Oczekiwanie na ENTER
iterację zadania async 1 iteracja zadania
asynchronicznego 2 iteracja zadania
asynchronicznego 3
iteracja
zadania asynchronicznego 4 iteracja
zadania asynchronicznego 5 iteracja
zadania asynchronicznego 6 iteracja
zadania asynchronicznego 7 iteracja
zadania asynchronicznego 8 iteracja zadania asynchronicznego 9
Wyjście Wykonać pracę()

Anri
źródło
1
Zdajesz sobie również sprawę, że blokuje dzwoniącego na 3 sekundy, zanim nawet powie mu zadanie, które może wykonać await? Oznacza to, że jeśli jest to wywoływane z wątku interfejsu użytkownika, zablokuje wątek interfejsu użytkownika na 3 sekundy? Ideą tego modelu jest unikanie robienia takich rzeczy.
Servy
1
@Servy tak, o to chodziło. Aby pokazać wszystkie etapy wykonania. To nie jest przykład z prawdziwego świata.
Anri,
7
@Servy trollujesz mnie?
Anri,
2
Nie. Próbuję pomóc Ci poprawić Twoją odpowiedź.
Servy
2
@ Anri ... Naprawdę doceniam twoje wysiłki tutaj. Wielkie dzięki!!
Praveen Prajapati
11

Dla każdego, kto nie ma doświadczenia w programowaniu asynchronicznym w .NET, oto (całkowicie fałszywa) analogia w scenariuszu, z którym możesz być lepiej zaznajomiony - wywołania AJAX przy użyciu JavaScript / jQuery. Prosty post jQuery AJAX wygląda następująco:

$.post(url, values, function(data) {
  // AJAX call completed, do something with returned data here
});

Powodem, dla którego przetwarzamy wyniki w funkcji zwrotnej, jest to, że nie blokujemy bieżącego wątku podczas oczekiwania na zwrot wywołania AJAX. Dopiero gdy odpowiedź jest gotowa, wywołanie zwrotne zostanie uruchomione, zwalniając bieżący wątek do wykonywania innych czynności w międzyczasie.

Teraz, jeśli JavaScript obsługuje await słowo kluczowe (co oczywiście nie ma ( jeszcze! )), Możesz osiągnąć to samo w ten sposób:

var data = await $.post(url, values);
// AJAX call completed, do something with returned data here

To dużo czystsze, ale z pewnością wygląda na to, że wprowadziliśmy synchroniczny, blokujący kod. Ale (fałszywy) kompilator JavaScript zająłby potem wszystkoawait i to z wywołaniem zwrotnym, więc w czasie wykonywania drugi przykład zachowywałby się tak samo jak pierwszy.

Może się wydawać, że nie oszczędza ci to dużo pracy, ale jeśli chodzi o takie rzeczy, jak obsługa wyjątków i konteksty synchronizacji, kompilator w rzeczywistości wykonuje dla ciebie dużo ciężkiej pracy. Aby uzyskać więcej informacji, polecam często zadawane pytania, a następnie serię blogów Stephena Cleary'ego .

Todd Menier
źródło
Trzymając się tej fałszywej analogii (która przy okazji bardzo mi pomogła, dzięki!), Co masz na myśli mówiąc „wszystko po”? Wszystko tylko w ramach tej samej funkcji (metody)? A może wszystko po nim, jak cokolwiek, co mogłoby zostać dodane do stosu wywołań?
2
„Wszystko po” = reszta metody. Kompilator efektywnie ponownie zapisuje pozostałą część metody jako wywołanie zwrotne, a sterowanie wraca natychmiast do obiektu wywołującego bieżącą metodę.
Todd Menier
1
Genialny Todd, jeszcze raz dziękuję za wyjaśnienie. Jestem pewien, że przydatne dla innych.
-2

Gdybym musiał to zaimplementować w Javie, wyglądałoby to mniej więcej tak:

/**
 * @author Ilya Gazman
 */
public abstract class SynchronizedTask{

    private ArrayList<Runnable> listeners = new ArrayList<Runnable>();

    private static final ThreadPoolExecutor threadPoolExecutor =  new ThreadPoolExecutor(6, 6, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(1000));

    public final void await(Runnable listener){
        synchronized (this) {
            listeners.add(listener);
        }
    }

    public void excecute(){
        onExcecute();
        for (int i = listeners.size() - 1; i >= 0; i--) {
            Runnable runnable;
            synchronized (this) {
                runnable = listeners.remove(i);
            }
            threadPoolExecutor.execute(runnable);
        }
    }

    protected abstract void onExcecute();
}

Twoja aplikacja użyje tego w następujący sposób:

public class Test{
    private Job job = new Job();

    public Test() {
        craeteSomeJobToRunInBackground();
        methode1();
        methode2();
    }

    private void methode1(){
        System.out.println("Running methode 1");
        job.await(new Runnable() {

            @Override
            public void run() {
                System.out.println("Continue to running methode 1");
            }
        });
    }

    private void methode2(){
        System.out.println("Running methode 2");
    }

    private void craeteSomeJobToRunInBackground() {
        new Thread(new Runnable() {

            @Override
            public void run() {
                job.excecute();
            }
        }).start();
    }

    private class Job extends SynchronizedTask{

        @Override
        protected void onExcecute() {
            try {
                Thread.sleep(1000);
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Job is done");
        }
    }
}
Ilya Gazman
źródło