private void RunAsync()
{
string param = "Hi";
Task.Run(() => MethodWithParameter(param));
}
private void MethodWithParameter(string param)
{
}
Edytować
Ze względu na popularne zapotrzebowanie muszę zauważyć, że Task
uruchomiony będzie działał równolegle z wątkiem wywołującym. Zakładając, że domyślnie TaskScheduler
będzie używany .NET ThreadPool
. W każdym razie oznacza to, że musisz wziąć pod uwagę wszelkie parametry przekazywane do elementu Task
jako potencjalnie dostępne dla wielu wątków jednocześnie, co czyni je współdzielonymi. Obejmuje to dostęp do nich w wątku wywołującym.
W moim powyższym kodzie sprawa ta jest całkowicie dyskusyjna. Ciągi znaków są niezmienne. Dlatego użyłem ich jako przykładu. Ale powiedz, że nie używasz String
...
Jednym z rozwiązań jest użycie async
i await
. To domyślnie spowoduje przechwycenie SynchronizationContext
wątku wywołującego i utworzy kontynuację dla pozostałej części metody po wywołaniu await
i dołączeniu go do utworzonego Task
. Jeśli ta metoda jest uruchomiona w wątku GUI WinForms, będzie to typ WindowsFormsSynchronizationContext
.
Kontynuacja zostanie uruchomiona po wysłaniu z powrotem do przechwyconych SynchronizationContext
- ponownie tylko domyślnie. Więc po await
rozmowie wrócisz do wątku, od którego zacząłeś . Możesz to zmienić na różne sposoby, w szczególności za pomocą ConfigureAwait
. W skrócie, reszta tej metody nie będą kontynuowane aż poTask
zakończył w innym wątku. Ale wątek wywołujący będzie nadal działał równolegle, ale nie reszta metody.
To oczekiwanie na zakończenie wykonywania pozostałej części metody może być pożądane lub nie. Jeśli nic w tej metodzie później nie uzyskuje dostępu do parametrów przekazanych do Task
, możesz w ogóle nie chcieć ich używać await
.
A może użyjesz tych parametrów znacznie później w metodzie. Nie ma powodu, by await
od razu kontynuować pracę. Pamiętaj, że możesz przechowywać Task
zwracaną wartość w zmiennej i await
na niej później - nawet w ten sam sposób. Na przykład, gdy potrzebujesz bezpiecznego dostępu do przekazanych parametrów po wykonaniu kilku innych czynności. Ponownie, nie musisz await
wchodzić w Task
prawo, gdy go uruchamiasz.
W każdym razie prostym sposobem na zapewnienie bezpieczeństwa wątku w odniesieniu do parametrów przekazywanych do Task.Run
jest wykonanie tego:
Najpierw trzeba ozdobić RunAsync
z async
:
private async void RunAsync()
Ważna uwaga
Najlepiej, aby oznaczona metoda nie zwracała wartości void, jak wspomniano w powiązanej dokumentacji. Typowym wyjątkiem są programy obsługi zdarzeń, takie jak kliknięcia przycisków i tym podobne. Muszą wrócić nieważne. W przeciwnym razie zawsze staram się zwrócić lub podczas używania . Jest to dobra praktyka z kilku powodów.async
Task
Task<TResult>
async
Teraz możesz await
uruchomić Task
jak poniżej. Nie możesz używać await
bez async
.
await Task.Run(() => MethodWithParameter(param));
Tak więc, ogólnie rzecz biorąc, jeśli wykonujesz await
zadanie, możesz uniknąć traktowania przekazanych w parametrach jako potencjalnie współdzielonego zasobu ze wszystkimi pułapkami modyfikowania czegoś z wielu wątków jednocześnie. Uważaj także na zamknięcia . Nie będę ich szczegółowo omawiać, ale artykuł, do którego prowadzi łącze, świetnie sobie z tym radzi.
Dygresja
Trochę poza tematem, ale bądź ostrożny przy używaniu wszelkiego rodzaju "blokowania" wątku GUI WinForms, ponieważ jest on oznaczony [STAThread]
. Używanie w await
ogóle nie blokuje, ale czasami widzę, że jest używane w połączeniu z jakimś rodzajem blokowania.
„Blok” jest w cudzysłowie, ponieważ technicznie nie można zablokować wątku GUI WinForms . Tak, jeśli używasz lock
w wątku GUI WinForms, nadal będzie on pompował komunikaty, mimo że myślisz, że jest „zablokowany”. To nie jest.
W bardzo rzadkich przypadkach może to powodować dziwne problemy. Na przykład jeden z powodów, dla których nigdy nie chcesz używać a lock
podczas malowania. Ale to jest marginalna i złożona sprawa; jednak widziałem, że powoduje to szalone problemy. Więc zanotowałem to dla kompletności.