7

mam standardowe działania non-asynchroniczny jak:Uruchamianie i Zapominając zadanie Async w MVC Akcja

[HttpPost] 
public JsonResult StartGeneratePdf(int id) 
{ 
    PdfGenerator.Current.GenerateAsync(id); 
    return Json(null); 
} 

Pomysł jest, że wiem, że to pokolenie PDF może trwać bardzo długo, więc po prostu rozpocząć zadanie i powrót, nie dbając o wynik operacji asynchronicznej.

W domyślnie ASP.Net MVC 4 Aplikacja ta daje mi ten miły wyjątek:

System.InvalidOperationException: asynchroniczny operacja nie może być uruchomiona w tym czasie. Operacje asynchroniczne można uruchamiać tylko w asynchronicznym programie obsługi lub module lub podczas określonych zdarzeń w cyklu życia strony. Jeśli wystąpił ten wyjątek podczas wykonywania strony, upewnij się, że strona jest oznaczona <% @ Page Async = "true"%>.

Co jest wszelkiego rodzaju nieistotne dla mojego scenariusza. Patrząc na nią można ustawić flagę na false, aby zapobiec tego wyjątek:

<appSettings> 
    <!-- Allows throwaway async operations from MVC Controller actions --> 
    <add key="aspnet:AllowAsyncDuringSyncStages" value="true" /> 
</appSettings> 

https://stackoverflow.com/a/15230973/176877
http://msdn.microsoft.com/en-us/library/hh975440.aspx

Ale pytanie brzmi, czy istnieje jakaś krzywda przez akurat tę operację Async i zapominając o tym od synchroniczne działanie kontrolera MVC? Wszystko, co mogę znaleźć, zaleca ustawienie kontrolera Async, ale to nie jest to, czego szukam - nie byłoby sensu, ponieważ powinien natychmiast powracać.

Odpowiedz

5

Relax, jak mówi sam Microsoft (http://msdn.microsoft.com/en-us/library/system.web.httpcontext.allowasyncduringsyncstages.aspx):

Takie zachowanie jest pomyślany jako zabezpieczenie, aby poinformować wcześnie jeśli piszesz kod asynchronicznej, co nie pasuje i oczekiwanych wzorców może mieć negatywne skutki uboczne.

Wystarczy zapamiętać kilka prostych zasad:

  • Nigdy czekają wewnątrz (asynchroniczny lub nie) void zdarzenia (jak wrócą natychmiast). Niektóre zdarzenia strony WebForms wspierają proste oczekiwania w nich - ale nadal najbardziej pożądanym podejściem jest RegisterAsyncTask.

  • Nie czekaj na asynchroniczne metody nieważne (ponieważ natychmiast wracają).

  • Nie czekaj synchronicznie w GUI lub zażądać wątku (.Wait(), .Result(), .WaitAll(), WaitAny()) metod asynchronicznych, które nie mają .ConfigureAwait(false) na korzeniu czekają wewnątrz nich, albo ich korzeń Task nie jest uruchamiany z .Run(), lub nie ma wyraźnego określenia TaskScheduler.Default (ponieważ GUI lub Request będą w ten sposób zakleszczone).

  • Zastosowanie .ConfigureAwait(false) lub Task.Run lub jawnie określić TaskScheduler.Default dla każdego procesu w tle, aw każdej metody biblioteki, który dokłada nie potrzebę dalszego kontekstu synchronizacji - myśleć o nim jako o „powołanie wątku”, ale wiem, że nie jest jeden (i nie zawsze na tym samym) i może już nie istnieć (jeśli Wniosek już się zakończył).Już samo to unika najczęstszych błędów asynchronicznych/oczekujących, a także zwiększa wydajność.

Microsoft po prostu założyć zapomniałeś czekać na zadaniu ...

UPDATE: Jako Stephen wyraźnie (gra słów nie przeznaczonych) stwierdził w swojej odpowiedzi, istnieje dziedziczą ale ukryte niebezpieczeństwo ze wszystkim formy podpalania i zapominania podczas pracy z pulami aplikacji, nie tylko specyficzne dla asynchronizacji/oczekiwania, ale również zadania, wątek i wszystkie inne metody - są one gwarantowane do zakończenia po zakończeniu żądania (są one objęte ochroną ,) w dowolnym momencie może zostać poddane recyklingowi z kilku powodów).

Może ci na tym zależy, czy nie (jeśli nie jest to krytyczne z punktu widzenia biznesu, jak w przypadku PO), ale powinieneś zawsze o tym wiedzieć.

+0

Ponieważ jest to naprawdę ostrzeżenie, to niefortunne, że nie można go przedstawić w ten sposób (na przykład jako ostrzeżenie dla konsoli wyjściowej w trybie debugowania). –

+0

Powiązana dokumentacja również nie jest bardzo bezpieczna - określają ją jako: "wykrywa aplikację niewłaściwie wykorzystującą interfejs API asynchronicznego". Być może ten wyjątek musi być oddzielony od bardziej podstępnego niebezpieczeństwa ", nadal jest niezrównana praca asynchroniczna, gdy kończy się asynchroniczny moduł lub kończy się sygnalizacja obsługi." –

+0

tak, to bardzo niefortunne, że nie ma jakiegoś atrybutu lub innego mechanizmu sygnalizującego, że w twoim przypadku jest to zamierzone (i całkowicie poprawne) zachowanie - ale jak nieobsłużone wyjątki zadań powodujące awarię aplikacji we wcześniejszej wersji (lepiej bezpieczniej niż przepraszam), być może następny rozwiąże ten wymóg również ... –

0

Odpowiedź okazuje się nieco bardziej skomplikowana:

Jeśli to, co robisz, jak w moim przykładzie jest tylko utworzenie długo działa zadanie asynchronicznej i powrocie, nie trzeba więcej niż to, co oświadczyłem w moim pytaniu.

Ale istnieje ryzyko: Jeśli ktoś rozwinął tę Akcję później, tam, gdzie ma to sens, aby Akcja była asynchroniczna, to metoda ognia i zapominania asynchronicznego wewnątrz niej będzie losowo udana lub nieudana. To wygląda następująco:

  1. Zakończono metodę podpalania i zapomnienia.
  2. Ponieważ został wyrzucony z wewnątrz zadania asynchronicznego, spróbuje ponownie dołączyć do kontekstu zadania ("marszałek") po powrocie.
  3. Jeśli działanie kontrolera asynchronicznego zostało zakończone, a instancja kontrolera od tego czasu została usunięta, ten kontekst zadania będzie teraz pusty.

Niezależnie od tego, czy w rzeczywistości wartość null będzie się różnić, z powodu powyższych czasów - czasami tak jest, czasami nie jest. Oznacza to, że programista może przetestować i znaleźć wszystko, co działa poprawnie, przejść do Produkcji i eksploduje. Co gorsza, przyczyną błędu jest:

  1. Wyjątek NullReferenceException - bardzo niejasny.
  2. Wbudowany kod .Net Framework, do którego nie można wejść nawet w Visual Studio - zazwyczaj System.Web.dll.
  3. Nie przechwycone przez żadną próbę/przechwycenie, ponieważ część biblioteki zadań, która umożliwia powrót do istniejących kontekstów try/catch, jest uszkodzoną częścią.

Otrzymasz tajemniczy błąd, gdy sprawy nie występują - Wyjątki są odrzucane, ale prawdopodobnie nie masz do nich dostępu. Niedobrze.

Czyste sposobem zapobiegania to:

[HttpPost] 
public JsonResult StartGeneratePdf(int id) 
{ 
    #pragma warning disable 4014 // Fire and forget. 
    Task.Run(async() => 
    { 
     await PdfGenerator.Current.GenerateAsync(id); 
    }).ConfigureAwait(false); 

    return Json(null); 
} 

Więc tutaj mamy synchroniczny kontroler bez problemów - lecz zapewnienie go jeszcze nie będzie, nawet jeśli zmienimy go ASYNC później, wyraźnie uruchom nowe zadanie za pomocą polecenia Uruchom, które domyślnie umieszcza zadanie w głównym wątku. Gdybyśmy na to czekali, spróbowaliby przywrócić to z powrotem do tego kontekstu, którego nie chcemy - więc nie czekamy na to, a to sprawia, że ​​mamy kłopotliwe ostrzeżenie. Wyłączamy ostrzeżenie z wyłączonym ostrzeżeniem pragma.

+0

masz rację, ale to jest obciążenie igły - po prostu upewnij się, że wszystkie twoje oczekiwania w 'PdfGenerator.Current.GenerateAsync' nie są w kontekście synchronizacji (zapoznaj się z' .ConfigureAwait (false) ') –

+0

musisz w pełni zrozumcie mój trzeci punkt biuletynu, zanim zrobicie cokolwiek innego async/poczekajcie na to - Stephen Cleary poniżej ma kilka bardzo dobrych artykułów na ten temat ... –

+0

@ NikolaBogdanović Czy przetestowałeś to? Uruchomienie za pomocą .ConfigureAwait (false) nie wystarczyło, aby zapobiec ponownemu wprowadzaniu zadania w pustym kontekście zadania; powyższe było. Nie jestem pewien, dlaczego twój trzeci punkt wypunktowania miałby tutaj jakieś znaczenie, ponieważ dotyczy wątku GUI i jest to działanie kontrolera ASP.Net. –

2

Urządzenie InvalidOperationException nie jest ostrzeżeniem. AllowAsyncDuringSyncStages to ustawienie niebezpieczne i takie, że ja osobiście nigdy nie będę używał .

Rozwiązanie polega na przechowywaniu żądania w kolejce trwałej (np. W kolejce Azure) i ma osobną aplikację (np. Rolę roli Azure) przetwarzającą w kolejce. To znacznie więcej pracy, ale jest to właściwy sposób. Mam na myśli "poprawne" w tym sensie, że recykling aplikacji IIS/ASP.NET nie zepsuje przetwarzania.

Jeśli absolutnie chcesz zachować przetwarzanie w pamięci (i, w następstwie, jesteś OK z czasem „utraty” reqeusts), to przynajmniej zarejestrować pracę z ASP.NET. Mam source code on my blog, w którym możesz skorzystać z rozwiązania. Ale proszę, nie chwytajcie za kod; przeczytaj cały post, aby było jasne, dlaczego nadal nie jest to najlepsze rozwiązanie. :)

+0

Aby wyjaśnić, praca, którą opisuję w pytaniu, nie jest niezbędna - jeśli AppDomain wysadzi się, serwer się rozpuści, cokolwiek, nie obchodzi mnie to. Albo mam ukończony plik PDF albo nie. Użytkownik nie zastanawia się, czy został stworzony - istnieje osobne połączenie sprawdzające jego status. Do tego rodzaju wyrzucania nie zgadzam się, że jest "niebezpieczny". –

+0

byłbyś całkiem poprawny, gdyby musiał on zwrócić jakąś odpowiedź, ale w jego przypadku nawet nie potrzebuje kontekstu (czy przeczytałeś całe pytanie) - więc oba twoje rozwiązania (jakkolwiek słusznie są ważne) w większości innych przypadków) byłoby tu po prostu strasznie napuchnięte igły na górze ... –

+0

@ NikolaBogdanović: Wcale; nie ma to nic wspólnego z tym, czy musi on odpowiedzieć; ma to związek z tym, czy wymaga on niezawodnego przetwarzania w tle. Hostowane w IIS domeny AppDomains i pule aplikacji są regularnie wyłączane lub poddawane recyklingowi * w przypadku braku aktywnych żądań *. Tak więc, moim pierwszym rozwiązaniem jest ** jedyna ** poprawna odpowiedź, jeśli dodatkowe przetwarzanie (w tym przypadku generowanie pliku PDF) jest krytyczne z biznesowego punktu widzenia. Moje drugie rozwiązanie informuje ASP.NET, że dodatkowe przetwarzanie trwa; w ogóle nie jest "strasznie rozdęty". –

Powiązane problemy