Klasa CancellationTokenSource
jest jednorazowa. Szybkie spojrzenie w Reflector dowodzi użycia KernelEvent
(bardzo prawdopodobne) niezarządzanego zasobu. Ponieważ CancellationTokenSource
nie ma finalizatora, jeśli go nie wyrzucimy, GC tego nie zrobi.
Z drugiej strony, jeśli spojrzysz na przykłady wymienione w artykule MSDN Anulowanie w zarządzanych wątkach , tylko jeden fragment kodu usuwa token.
Jaki jest właściwy sposób pozbycia się go w kodzie?
- Nie możesz opakować kodu rozpoczynającego równoległe zadanie,
using
jeśli nie czekasz na to. Anulowanie ma sens tylko wtedy, gdy nie czekasz. - Oczywiście możesz dodać
ContinueWith
zadanie,Dispose
dzwoniąc, ale czy to właściwy sposób? - A co z anulowanymi zapytaniami PLINQ, które nie są synchronizowane z powrotem, ale po prostu coś robią na końcu? Powiedzmy
.ForAll(x => Console.Write(x))
? - Czy to jest wielokrotnego użytku? Czy ten sam token może być używany do kilku wywołań, a następnie usuwać go razem z komponentem hosta, powiedzmy, kontrolką interfejsu użytkownika?
Ponieważ nie ma czegoś takiego jak Reset
metoda czyszczenia IsCancelRequested
i Token
pola, przypuszczam, że nie można jej użyć ponownie, więc za każdym razem, gdy zaczynasz zadanie (lub zapytanie PLINQ), powinieneś utworzyć nowe. Czy to prawda? Jeśli tak, moje pytanie brzmi: jaka jest poprawna i zalecana strategia postępowania Dispose
w tych wielu CancellationTokenSource
przypadkach?
źródło
Important: The CancellationTokenSource class implements the IDisposable interface. You should be sure to call the CancellationTokenSource.Dispose method when you have finished using the cancellation token source to free any unmanaged resources it holds.
- docs.microsoft.com/en-us/dotnet/standard/threading/…Uważam, że żadna z obecnych odpowiedzi nie była zadowalająca. Po zbadaniu informacji znalazłem odpowiedź od Stephena Toub ( odniesienie ):
Uważam, że odważna część jest ważną częścią. Używa określenia „bardziej wpływowy”, co pozostawia go nieco niejasnym. Interpretuję to jako oznaczenie, że dzwonienie
Dispose
w takich sytuacjach powinno być wykonywane, w przeciwnym razie używanieDispose
nie jest potrzebne.źródło
Zajrzałem do ILSpy,
CancellationTokenSource
ale mogę znaleźć tylko,m_KernelEvent
która jest faktycznie aManualResetEvent
, która jest klasą opakowującą dlaWaitHandle
obiektu. Powinno to być odpowiednio obsługiwane przez GC.źródło
Zawsze powinieneś się pozbyć
CancellationTokenSource
.Sposób pozbycia się tego zależy dokładnie od scenariusza. Proponujesz kilka różnych scenariuszy.
using
działa tylko wtedy, gdy wykonujeszCancellationTokenSource
równoległą pracę, na którą czekasz. Jeśli to twój senario, to świetnie, to najłatwiejsza metoda.Podczas korzystania z zadań użyj
ContinueWith
wskazanego zadania do usunięciaCancellationTokenSource
.Plinq można użyć,
using
ponieważ uruchamiasz go równolegle, ale czekasz na zakończenie wszystkich równolegle działających procesów roboczych .W przypadku interfejsu użytkownika można utworzyć nową
CancellationTokenSource
dla każdej operacji, którą można anulować, która nie jest powiązana z żadnym wyzwalaczem anulowania. ZachowajList<IDisposable>
i dodaj każde źródło do listy, usuwając je wszystkie po usunięciu komponentu.W przypadku wątków utwórz nowy wątek, który łączy wszystkie wątki robocze i zamyka pojedyncze źródło po zakończeniu wszystkich wątków roboczych. Zobacz CancellationTokenSource, kiedy usunąć?
Zawsze jest sposób.
IDisposable
wystąpienia należy zawsze usuwać. Próbki często nie są takie, ponieważ są albo szybkimi próbkami pokazującymi podstawowe użycie, albo ponieważ dodanie wszystkich aspektów prezentowanej klasy byłoby zbyt skomplikowane dla próbki. Próbka jest po prostu próbką, niekoniecznie (lub nawet zwykle) kodem jakości produkcji. Nie wszystkie próbki można skopiować do kodu produkcyjnego w takiej postaci, w jakiej są.źródło
await
w zadaniu i usunąć CancellationTokenSource w kodzie, który pojawia się po oczekiwaniu?await
operacji, możesz wznowić z powoduOperationCanceledException
. Możesz wtedy zadzwonićDispose()
. Ale jeśli istnieją operacje nadal działające i używające odpowiednichCancellationToken
, ten token nadal zgłasza sięCanBeCanceled
jako,true
mimo że źródło zostało usunięte. Jeśli spróbują zarejestrować oddzwonienie w sprawie anulowania, BOOM! ,ObjectDisposedException
. Wystarczy zadzwonićDispose()
po pomyślnym zakończeniu operacji. To staje się naprawdę trudne, gdy naprawdę musisz coś anulować.Ta odpowiedź wciąż pojawia się w wyszukiwaniach Google i uważam, że głosowana odpowiedź nie zawiera pełnej historii. Po patrząc na kod źródłowy dla
CancellationTokenSource
(CTS) iCancellationToken
(CT) Wierzę, że dla większości przypadków użycia następujących sekwencji kodu jest w porządku:m_kernelHandle
Pole wewnętrzne wspomniano powyżej jest przedmiotem synchronizacja kopii naWaitHandle
nieruchomość w obu klasach CTS i CT. Jest tworzony tylko wtedy, gdy masz dostęp do tej właściwości. Tak więc, jeśli nie używaszWaitHandle
starej szkoły synchronizacji wątków wTask
usuwaniu wywołań, nie przyniesie to żadnego efektu.Oczywiście, jeśli są użyciem należy zrobić, co jest sugerowane przez innych odpowiedzi powyżej opóźnienia powołania
Dispose
aż wszystkieWaitHandle
operacje za pomocą uchwytu są kompletne, ponieważ, jak to jest opisane w dokumentacji API Windows dla WaitHandle , wyniki są niezdefiniowane.źródło
IsCancellationRequested
właściwości tokenu przez odpytywanie, wywołanie zwrotne lub dojście do oczekiwania”. Innymi słowy: to nie Ty (tj. Osoba wysyłająca żądanie asynchroniczne) możesz użyć uchwytu oczekiwania, może to być słuchacz (tj. Osoba odpowiadająca na żądanie). Oznacza to, że jako osoba odpowiedzialna za usuwanie nie masz żadnej kontroli nad tym, czy uchwyt oczekiwania jest używany, czy nie.Minęło dużo czasu, odkąd o to zapytałem i otrzymałem wiele pomocnych odpowiedzi, ale natknąłem się na interesujący problem związany z tym i pomyślałem, że opublikuję go tutaj jako kolejną odpowiedź:
Powinieneś dzwonić
CancellationTokenSource.Dispose()
tylko wtedy, gdy jesteś pewien, że nikt nie będzie próbował zdobyćToken
własności CTS . W przeciwnym razie nie powinieneś tego nazywać, ponieważ jest to wyścig. Na przykład zobacz tutaj:https://github.com/aspnet/AspNetKatana/issues/108
W rozwiązaniu tego problemu kod, który poprzednio robił,
cts.Cancel(); cts.Dispose();
został zmieniony tak, aby zrobić,cts.Cancel();
ponieważ każdy, kto ma pecha, aby spróbować uzyskać token anulowania, aby obserwować jego stan anulowania poDispose
wywołaniu, niestety będzie musiał również obsłużyćObjectDisposedException
- opróczOperationCanceledException
że planowali.Inną kluczową obserwacją związaną z tą poprawką jest Tratcher: „Utylizacja jest wymagana tylko w przypadku tokenów, które nie zostaną anulowane, ponieważ anulowanie powoduje takie samo czyszczenie”. tzn. po prostu robienie
Cancel()
zamiast wyrzucania jest naprawdę wystarczająco dobre!źródło
Stworzyłem klasę bezpieczną dla wątków, która wiąże a
CancellationTokenSource
z aTask
i gwarantuje, żeCancellationTokenSource
zostanie usunięta, gdy skojarzona z nim zostanieTask
zakończona. Używa blokad, aby zapewnić, żeCancellationTokenSource
nie zostaną anulowane podczas lub po ich usunięciu . Dzieje się tak w celu zachowania zgodności z dokumentacją , która stwierdza:A także :
Oto klasa:
Podstawowe metody tej
CancelableExecution
klasy toRunAsync
iCancel
. Domyślnie operacje współbieżne nie są dozwolone, co oznacza, że wywołanieRunAsync
po raz drugi spowoduje ciche anulowanie i oczekiwanie na zakończenie poprzedniej operacji (jeśli nadal jest uruchomiona) przed rozpoczęciem nowej operacji.Ta klasa może być używana w dowolnych aplikacjach. Jego głównym zastosowaniem jest jednak w aplikacjach UI, wewnątrz formularzy z przyciskami do uruchamiania i anulowania operacji asynchronicznej lub z listą, która anuluje i ponownie uruchamia operację za każdym razem, gdy zostanie zmieniony jej wybrany element. Oto przykład pierwszego przypadku:
RunAsync
Sposób przyjmuje dodatkowąCancellationToken
jako argumentu, który jest połączony z utworzony wewnętrznieCancellationTokenSource
. Dostarczenie tego opcjonalnego tokena może być przydatne w scenariuszach zaawansowanych.źródło