Odpowiedzi:
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
.
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śli SynchronizationContext.Current
jest null
.
Jest efektywnie wdrażany jako niestandardowe oczekiwanie. Mniej wydajny kod dający identyczny efekt może być tak prosty:
var tcs = new TaskCompletionSource<bool>();
var sc = SynchronizationContext.Current;
if (sc != null)
sc.Post(_ => tcs.SetResult(true), null);
else
ThreadPool.QueueUserWorkItem(_ => tcs.SetResult(true));
await tcs.Task;
Task.Yield()
może być użyty jako skrót do niektórych dziwnych zmian przepływu wykonania. Na przykład:
async Task DoDialogAsync()
{
var dialog = new Form();
Func<Task> showAsync = async () =>
{
await Task.Yield();
dialog.ShowDialog();
}
var dialogTask = showAsync();
await Task.Yield();
// now we're on the dialog's nested message loop started by dialog.ShowDialog
MessageBox.Show("The dialog is visible, click OK to close");
dialog.Close();
await dialogTask;
// we're back to the main message loop
}
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ż:
var dialogTask = await showAsync();
?
var dialogTask = await showAsync()
nie skompiluje się, ponieważ await showAsync()
wyrażenie nie zwraca a Task
(inaczej niż bez await
). To powiedziawszy, jeśli to zrobisz await showAsync()
, wykonanie po nim zostanie wznowione dopiero po zamknięciu okna dialogowego, właśnie tak jest inaczej. To dlatego, że window.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ę.
private static void Main()
{
RecursiveMethod().Wait();
}
private static async Task RecursiveMethod()
{
await Task.Delay(1);
//await Task.Yield(); // Uncomment this line to prevent stackoverlfow.
await RecursiveMethod();
}
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.
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.