Przedmowa : Szukam wyjaśnienia, a nie tylko rozwiązania. Znam już rozwiązanie.
Mimo, że spędziłem kilka dni na studiowaniu artykułów MSDN na temat Asynchronicznego Wzorca Asynchronicznego (TAP), asynchronizacji i czekania, nadal jestem nieco zdezorientowany niektórymi szczegółami.
Piszę program rejestrujący dla aplikacji ze Sklepu Windows i chcę obsługiwać zarówno rejestrowanie asynchroniczne, jak i synchroniczne. Metody asynchroniczne są zgodne z TAP, metody synchroniczne powinny to wszystko ukrywać i wyglądać i działać jak zwykłe metody.
Jest to podstawowa metoda rejestrowania asynchronicznego:
private async Task WriteToLogAsync(string text)
{
StorageFolder folder = ApplicationData.Current.LocalFolder;
StorageFile file = await folder.CreateFileAsync("log.log",
CreationCollisionOption.OpenIfExists);
await FileIO.AppendTextAsync(file, text,
Windows.Storage.Streams.UnicodeEncoding.Utf8);
}
Teraz odpowiednia metoda synchroniczna ...
Wersja 1 :
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.Wait();
}
Wygląda to poprawnie, ale nie działa. Cały program zawiesza się na zawsze.
Wersja 2 :
Hmm .. Może zadanie nie zostało rozpoczęte?
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.Start();
task.Wait();
}
To rzuca InvalidOperationException: Start may not be called on a promise-style task.
Wersja 3:
Hmm .. Task.RunSynchronously
brzmi obiecująco.
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.RunSynchronously();
}
To rzuca InvalidOperationException: RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.
Wersja 4 (rozwiązanie):
private void WriteToLog(string text)
{
var task = Task.Run(async () => { await WriteToLogAsync(text); });
task.Wait();
}
To działa. Zatem 2 i 3 to niewłaściwe narzędzia. Ale 1? Co jest nie tak z 1 i jaka jest różnica do 4? Co powoduje, że 1 powoduje zawieszenie się? Czy jest jakiś problem z obiektem zadania? Czy istnieje nieoczywisty impas?
źródło
Odpowiedzi:
await
Wewnątrz asynchronicznej metody próbuje wrócić do wątku UI.Ponieważ wątek interfejsu użytkownika jest zajęty i czeka na zakończenie całego zadania, masz impas.
Przeniesienie wywołania asynchronicznego
Task.Run()
rozwiązuje problem.Ponieważ wywołanie asynchroniczne działa teraz w wątku puli wątków, nie próbuje wrócić do wątku interfejsu użytkownika i dlatego wszystko działa.
Alternatywnie możesz zadzwonić
StartAsTask().ConfigureAwait(false)
przed oczekiwaniem na operację wewnętrzną, aby powrócić do puli wątków zamiast do wątku interfejsu użytkownika, całkowicie unikając impasu.źródło
ConfigureAwait(false)
tym przypadku jest właściwe rozwiązanie. Ponieważ nie ma potrzeby wywoływania wywołań zwrotnych w przechwyconym kontekście, nie powinno. Będąc metodą API, powinien obsługiwać ją wewnętrznie, zamiast zmuszać wszystkich wywołujących do opuszczenia kontekstu interfejsu użytkownika.Microsoft.Bcl.Async
.Wywołanie
async
kodu z kodu synchronicznego może być dość trudne.Na moim blogu wyjaśniam pełne przyczyny tego impasu . Krótko mówiąc, istnieje „kontekst”, który jest domyślnie zapisywany na początku każdego
await
i wykorzystywany do wznowienia metody.Jeśli więc zostanie to wywołane w kontekście interfejsu użytkownika, po
await
zakończeniuasync
metoda podejmie próbę ponownego wprowadzenia tego kontekstu, aby kontynuować wykonywanie. Niestety kod używającyWait
(lubResult
) zablokuje wątek w tym kontekście, więcasync
metoda nie może zostać zakończona.Wytyczne, aby tego uniknąć, to:
ConfigureAwait(continueOnCapturedContext: false)
jak najwięcej. Umożliwiaasync
to kontynuowanie wykonywania metod bez konieczności ponownego wchodzenia w kontekst.async
do końca. Użyjawait
zamiastResult
lubWait
.Jeśli twoja metoda jest naturalnie asynchroniczna, to (prawdopodobnie) nie powinieneś wystawiać synchronicznego opakowania .
źródło
async
sposobu, w jaki to mam zrobić i zapobiec sytuacji pożaru i zapomnienia.await
jest obsługiwany wcatch
blokach od VS2015. Jeśli korzystasz ze starszej wersji, możesz przypisać wyjątek do zmiennej lokalnej i wykonaćawait
blok po catch .Oto co zrobiłem
działa świetnie i nie blokuje wątku interfejsu użytkownika
źródło
Przy małym niestandardowym kontekście synchronizacji funkcja synchronizacji może czekać na zakończenie funkcji asynchronicznej bez tworzenia impasu. Oto mały przykład aplikacji WinForms.
źródło