Używam async / czekaj i Task
dużo, ale nigdy nie korzystałem Task.Yield()
i szczerze mówiąc, pomimo wszystkich wyjaśnień, nie rozumiem, dlaczego potrzebuję tej metody.
Czy ktoś może podać dobry przykład, gdzie Yield()
jest to wymagane?
źródło
Używam async / czekaj i Task
dużo, ale nigdy nie korzystałem Task.Yield()
i szczerze mówiąc, pomimo wszystkich wyjaśnień, nie rozumiem, dlaczego potrzebuję tej metody.
Czy ktoś może podać dobry przykład, gdzie Yield()
jest to wymagane?
Kiedy używasz async
/ await
, nie ma gwarancji, że metoda, którą wywołasz, kiedy to zrobisz, await FooAsync()
będzie działać asynchronicznie. Wewnętrzna implementacja może powrócić za pomocą całkowicie synchronicznej ścieżki.
Jeśli tworzysz interfejs API, w którym bardzo ważne jest, aby nie blokować i uruchamiać jakiś kod asynchronicznie, a istnieje szansa, że wywoływana metoda będzie działać synchronicznie (efektywnie blokując), użycie await Task.Yield()
spowoduje wymuszenie asynchroniczności metody i zwróci kontrola w tym momencie. Reszta kodu zostanie wykonana w późniejszym czasie (w tym momencie nadal może działać synchronicznie) w bieżącym kontekście.
Może to być również przydatne, jeśli wykonasz metodę asynchroniczną, która wymaga pewnej „długofalowej” inicjalizacji, tj .:
private async void button_Click(object sender, EventArgs e)
{
await Task.Yield(); // Make us async right away
var data = ExecuteFooOnUIThread(); // This will run on the UI thread at some point later
await UseDataAsync(data);
}
Bez Task.Yield()
wywołania metoda będzie wykonywana synchronicznie aż do pierwszego wywołania await
.
await Task.Yield()
zmusza metodę do asynchronizacji, dlaczego mielibyśmy zawracać sobie głowę pisaniem „prawdziwego” kodu asynchronicznego? Wyobraź sobie metodę intensywnej synchronizacji. Aby było asynchroniczne, wystarczy dodać,async
aawait Task.Yield()
na początku i magicznie będzie asynchroniczne? To byłoby prawie jak zawijanie całego kodu synchronizacjiTask.Run()
i tworzenie fałszywej metody asynchronicznej.Task.Run
zaimplementować go,ExecuteFooOnUIThread
będzie działać na puli wątków, a nie na wątku interfejsu użytkownika. Za pomocąawait Task.Yield()
wymusza się jego asynchroniczność w taki sposób, że kolejny kod jest nadal uruchamiany w bieżącym kontekście (tylko w późniejszym momencie). To nie jest coś, co zwykle robisz, ale fajnie, że istnieje opcja, jeśli jest wymagana z jakiegoś dziwnego powodu.ExecuteFooOnUIThread()
działało bardzo długo, w pewnym momencie nadal blokowałoby wątek interfejsu użytkownika i powodowało brak reakcji interfejsu, czy to prawda?Wewnętrznie
await Task.Yield()
po prostu kolejkuje kontynuację w bieżącym kontekście synchronizacji lub w wątku losowej puli, jeśliSynchronizationContext.Current
jestnull
.Jest efektywnie wdrażany jako niestandardowe oczekiwanie. Mniej wydajny kod dający identyczny efekt może być tak prosty:
Task.Yield()
może być użyty jako skrót do niektórych dziwnych zmian przepływu wykonania. Na przykład:To powiedziawszy, nie mogę wymyślić żadnego przypadku, w którym
Task.Yield()
nie można go zastąpićTask.Factory.StartNew
w / właściwym harmonogramem zadań.Zobacz też:
„Oczekuj na Task.Yield ()” i jego alternatywy
Task.Yield - prawdziwe zastosowania?
źródło
var dialogTask = await showAsync();
?var dialogTask = await showAsync()
nie skompiluje się, ponieważawait showAsync()
wyrażenie nie zwraca aTask
(inaczej niż bezawait
). To powiedziawszy, jeśli to zrobiszawait showAsync()
, wykonanie po nim zostanie wznowione dopiero po zamknięciu okna dialogowego, właśnie tak jest inaczej. To dlatego, żewindow.ShowDialog
jest synchronicznym interfejsem API (mimo że nadal pompuje wiadomości). W tym kodzie chciałem kontynuować, dopóki okno dialogowe jest nadal wyświetlane.Jednym z zastosowań
Task.Yield()
jest zapobieganie przepełnieniu stosu podczas wykonywania rekurencji asynchronicznej.Task.Yield()
zapobiega synchronicznej kontynuacji. Należy jednak pamiętać, że może to spowodować wyjątek OutOfMemory (jak zauważył Triynko). Niekończąca się rekurencja wciąż nie jest bezpieczna i prawdopodobnie lepiej jest przepisać rekurencję jako pętlę.źródło
await Task.Delay(1)
wystarczy, aby temu zapobiec. (Aplikacja konsoli, .NET Core 3.1, C # 8)Task.Yield()
może być stosowany w próbnych implementacjach metod asynchronicznych.źródło