Słowo kluczowe Oczekiwanie w C # (.NET Async CTP) jest niedozwolone w instrukcji lock.
Z MSDN :
Wyrażenia oczekującego nie można użyć w funkcji synchronicznej, w wyrażeniu zapytania, w instrukcji catch lub wreszcie instrukcji obsługi wyjątku, w bloku instrukcji lock lub w niebezpiecznym kontekście.
Zakładam, że z jakiegoś powodu zespół kompilatora jest trudny lub niemożliwy do wdrożenia.
Próbowałem obejść instrukcję using:
class Async
{
public static async Task<IDisposable> Lock(object obj)
{
while (!Monitor.TryEnter(obj))
await TaskEx.Yield();
return new ExitDisposable(obj);
}
private class ExitDisposable : IDisposable
{
private readonly object obj;
public ExitDisposable(object obj) { this.obj = obj; }
public void Dispose() { Monitor.Exit(this.obj); }
}
}
// example usage
using (await Async.Lock(padlock))
{
await SomethingAsync();
}
Nie działa to jednak zgodnie z oczekiwaniami. Wywołanie Monitor.Exit w ExitDisposable.Dispose wydaje się blokować na czas nieokreślony (przez większość czasu), powodując zakleszczenia, gdy inne wątki próbują uzyskać blokadę. Podejrzewam, że niewiarygodność mojej pracy, a powody, dla których oświadczenia oczekujące są niedozwolone, są w jakiś sposób powiązane.
Czy ktoś wie, dlaczego czekanie nie jest dozwolone w treści instrukcji blokady?
źródło
Odpowiedzi:
Nie, wcale nie jest to trudne ani niemożliwe do wdrożenia - fakt, że sam go wdrożyłeś, świadczy o tym. Jest to raczej bardzo zły pomysł, dlatego nie pozwalamy na to, aby uchronić cię przed popełnieniem tego błędu.
Zgadza się, odkryłeś, dlaczego wprowadziliśmy to w nielegalność. Oczekiwanie wewnątrz zamka to przepis na wytwarzanie zakleszczeń.
Jestem pewien, że rozumiesz, dlaczego: dowolny kod jest uruchamiany między czasem, w którym oczekiwanie zwraca kontrolę nad programem wywołującym, a metoda wznawia działanie . Tym dowolnym kodem może być usuwanie blokad, które powodują odwrócenie kolejności blokowania, a tym samym zakleszczenia.
Co gorsza, kod może zostać wznowiony w innym wątku (w zaawansowanych scenariuszach; zwykle odbiera się ponownie w wątku, który czekał, ale niekoniecznie), w którym to przypadku odblokowanie odblokowuje zamek w innym wątku niż wątek, który wziął z zamka. Czy to dobry pomysł? Nie.
Zauważam, że jest to również „najgorsza praktyka” robić
yield return
wnętrze zlock
tego samego powodu. Jest to zgodne z prawem, ale szkoda, że nie uczyniliśmy tego nielegalnym. Nie popełnimy tego samego błędu co do „czekania”.źródło
SemaphoreSlim.WaitAsync
klasa została dodana do środowiska .NET długo po opublikowaniu tej odpowiedzi, myślę, że możemy bezpiecznie założyć, że jest to teraz możliwe. Niezależnie od tego, twoje komentarze na temat trudności w implementacji takiego konstruktu są nadal w pełni aktualne.Użyj
SemaphoreSlim.WaitAsync
metodyźródło
Stuff
, nie widzę żadnego sposobu na obejście tego ...mySemaphoreSlim = new SemaphoreSlim(1, 1)
, aby działał jaklock(...)
?Zasadniczo byłoby to niewłaściwe.
Można to zrealizować na dwa sposoby :
Trzymaj zamek, zwalniając go tylko na końcu bloku .
To naprawdę zły pomysł, ponieważ nie wiesz, ile czasu zajmie operacja asynchroniczna. Blokady należy trzymać tylko przez minimalny czas. Jest to również potencjalnie niemożliwe, ponieważ wątek posiada blokadę, a nie metodę - a nawet nie można wykonać reszty metody asynchronicznej w tym samym wątku (w zależności od harmonogramu zadań).
Zwolnij blokadę w oczekiwaniu i ponownie ją uzyskaj, gdy oczekiwanie powróci.
To narusza zasadę najmniejszego zdziwienia IMO, w której metoda asynchroniczna powinna zachowywać się tak dokładnie, jak to możliwe, jak równoważny kod synchroniczny - chyba że użyjesz
Monitor.Wait
w bloku blokady, spodziewaj się posiadać zamek na czas trwania bloku.Zasadniczo istnieją tutaj dwa konkurujące wymagania - nie powinieneś próbować robić pierwszego tutaj, a jeśli chcesz zastosować drugie podejście, możesz uczynić kod znacznie wyraźniejszym poprzez oddzielenie dwóch bloków blokujących oddzielonych wyrażeniem oczekującym:
Tak więc zakazując ci czekania w samym bloku blokady, język zmusza cię do myślenia o tym, co naprawdę chcesz zrobić, i czyni wybór bardziej przejrzystym w pisanym kodzie.
źródło
SemaphoreSlim.WaitAsync
klasa została dodana do środowiska .NET długo po opublikowaniu tej odpowiedzi, myślę, że możemy bezpiecznie założyć, że jest to teraz możliwe. Niezależnie od tego, twoje komentarze na temat trudności w implementacji takiego konstruktu są nadal w pełni aktualne.To tylko rozszerzenie tej odpowiedzi .
Stosowanie:
źródło
try
blokiem może być niebezpieczna - jeśli zdarzy się wyjątek między,WaitAsync
atry
semafor nigdy nie zostanie zwolniony (zakleszczenie). Z drugiej strony przeniesienieWaitAsync
wywołania dotry
bloku wprowadzi inny problem, gdy semafor może zostać zwolniony bez uzyskiwania blokady. Zobacz pokrewny wątek, w którym wyjaśniono ten problem: stackoverflow.com/a/61806749/7889645Odnosi się to do http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx , http://winrtstoragehelper.codeplex.com/ , sklepu z aplikacjami Windows 8 i .net 4.5
Oto mój punkt widzenia na to:
Funkcja języka asynchronicznego / oczekującego sprawia, że wiele rzeczy jest dość łatwych, ale wprowadza również scenariusz, z którym rzadko się zdarzało, zanim było tak łatwe w użyciu wywołania asynchroniczne: ponowne wejście.
Jest to szczególnie prawdziwe w przypadku procedur obsługi zdarzeń, ponieważ w przypadku wielu zdarzeń nie masz pojęcia, co się dzieje po powrocie z procedury obsługi zdarzeń. Jedną z rzeczy, które mogą się zdarzyć, jest to, że metoda asynchroniczna, na którą czekasz w pierwszej procedurze obsługi zdarzeń, jest wywoływana z innej procedury obsługi zdarzeń, która wciąż znajduje się w tym samym wątku.
Oto prawdziwy scenariusz, z którym spotkałem się w aplikacji Windows 8 App Store: Moja aplikacja ma dwie ramki: wchodzenie i wychodzenie z ramki Chcę załadować / zabezpieczyć niektóre dane do pliku / pamięci. Zdarzenia OnNavigatedTo / From służą do zapisywania i ładowania. Zapisywanie i ładowanie odbywa się za pomocą funkcji narzędzia asynchronicznego (np. Http://winrtstoragehelper.codeplex.com/ ). Podczas nawigacji z ramki 1 do ramki 2 lub w innym kierunku, ładowanie asynchroniczne i operacje bezpieczne są wywoływane i oczekiwane. Procedury obsługi zdarzeń stają się asynchroniczne zwracając void => nie można się doczekać.
Jednak pierwsza operacja otwierania pliku (powiedzmy: wewnątrz funkcji składowania) narzędzia jest również asynchroniczna, więc pierwsze oczekiwanie zwraca kontrolę do frameworka, który później wywołuje inne narzędzie (ładowanie) za pośrednictwem drugiej procedury obsługi zdarzeń. Ładunek próbuje teraz otworzyć ten sam plik, a jeśli plik jest już otwarty dla operacji składowania, kończy się niepowodzeniem z wyjątkiem ACCESSDENIED.
Minimalnym rozwiązaniem dla mnie jest zabezpieczenie dostępu do pliku za pomocą przy użyciu i AsyncLock.
Należy pamiętać, że jego blokada zasadniczo blokuje wszystkie operacje na plikach dla narzędzia za pomocą tylko jednej blokady, co jest niepotrzebnie silne, ale działa dobrze w moim scenariuszu.
Oto mój projekt testowy: aplikacja Windows 8 App Store z kilkoma testowymi wywołaniami oryginalnej wersji z http://winrtstoragehelper.codeplex.com/ i mojej zmodyfikowanej wersji, która używa AsyncLock z Stephen Toub http: //blogs.msdn. com / b / pfxteam / archive / 2012/02/12 / 10266988.aspx .
Czy mogę również zasugerować ten link: http://www.hanselman.com/blog/ComparingTwoTechniquesInNETAsynchronousCoordinationPrimitives.aspx
źródło
Stephen Taub zaimplementował rozwiązanie tego pytania, patrz Budowanie prymityw koordynacji asynchronicznej, część 7: AsyncReaderWriterLock .
Stephen Taub jest wysoko ceniony w branży, więc wszystko, co pisze, może być solidne.
Nie będę reprodukować kodu, który opublikował na swoim blogu, ale pokażę ci, jak go użyć:
Jeśli chcesz metodę, która jest upieczona w środowisku .NET, użyj
SemaphoreSlim.WaitAsync
zamiast tego. Nie dostaniesz blokady czytnika / zapisu, ale otrzymasz wypróbowaną i przetestowaną implementację.źródło
SemaphoreSlim.WaitAsync
w środowisku .NET. Wszystko, co robi ten kod, to dodanie koncepcji blokady czytnika / pisarza.Hmm, wygląda brzydko, wydaje się działać.
źródło
Próbowałem użyć Monitora (kod poniżej), który wydaje się działać, ale ma GOTCHA ... gdy masz wiele wątków, to da ... System.Threading.SynchronizationLockException Metoda synchronizacji obiektu została wywołana z niezsynchronizowanego bloku kodu.
Wcześniej po prostu to robiłem, ale było to w kontrolerze ASP.NET, co spowodowało impas.
public async Task<FooResponse> ModifyFooAsync() { lock(lockObject) { return SomeFunctionToModifyFooAsync.Result; } }
źródło