Czy ktoś może wyjaśnić, co await
robi ta funkcja?
Czy ktoś może wyjaśnić, co await
robi ta funkcja?
Odpowiedzi:
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();
Zasadniczo słowa kluczowe async
i await
pozwalają 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 return
instrukcji 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 return
znaku, 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 async
musi 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 return
upraszczają 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.
Obecnie akceptowana odpowiedź jest myląca.
await
niczego nie wstrzymuje. Przede wszystkim może być używany tylko w metodach lub lambdach oznaczonych jako async
i zwracających Task
lub void
jeśli nie zależy ci na tym, aby Task
instancja 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ę()
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.
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 .
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");
}
}
}