Pracuję nad wielozadaniowym projektem sieciowym i jestem nowy Threading.Tasks
. Zaimplementowałem prosty Task.Factory.StartNew()
i zastanawiam się, jak mogę to zrobić Task.Run()
?
Oto podstawowy kod:
Task.Factory.StartNew(new Action<object>(
(x) =>
{
// Do something with 'x'
}), rawData);
Spojrzałem System.Threading.Tasks.Task
w Object Browser i nie mogłem znaleźć Action<T>
takiego parametru. Jest tylko Action
taki, który przyjmuje void
parametr, a nie typ .
Są tylko dwie podobne rzeczy: static Task Run(Action action)
i static Task Run(Func<Task> function)
ale nie można wysyłać parametrów z obydwoma.
Tak, wiem, że mogę stworzyć dla niego prostą metodę rozszerzenia, ale moje główne pytanie brzmi: czy możemy napisać to w jednej linii za pomocą Task.Run()
?
c#
lambda
task-parallel-library
task
MFatihMAR
źródło
źródło
rawData
to sieciowy pakiet danych, który ma klasę kontenera (np. DataPacket) i ponownie używam tej instancji, aby zmniejszyć ciśnienie GC. Tak więc, jeśli używamrawData
bezpośrednio wTask
, można to (prawdopodobnie) zmienić przedTask
obsługą. Teraz myślę, że mogę stworzyćbyte[]
dla niego inną instancję. Myślę, że to dla mnie najprostsze rozwiązanie.Action<byte[]>
tego nie zmienia.Odpowiedzi:
private void RunAsync() { string param = "Hi"; Task.Run(() => MethodWithParameter(param)); } private void MethodWithParameter(string param) { //Do stuff }
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ślnieTaskScheduler
będzie używany .NETThreadPool
. W każdym razie oznacza to, że musisz wziąć pod uwagę wszelkie parametry przekazywane do elementuTask
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
iawait
. To domyślnie spowoduje przechwycenieSynchronizationContext
wątku wywołującego i utworzy kontynuację dla pozostałej części metody po wywołaniuawait
i dołączeniu go do utworzonegoTask
. Jeśli ta metoda jest uruchomiona w wątku GUI WinForms, będzie to typWindowsFormsSynchronizationContext
.Kontynuacja zostanie uruchomiona po wysłaniu z powrotem do przechwyconych
SynchronizationContext
- ponownie tylko domyślnie. Więc poawait
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 iawait
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 musiszawait
wchodzić wTask
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
zasync
: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
bezasync
.await Task.Run(() => MethodWithParameter(param)); //Code here and below in the same method will not run until AFTER the above task has completed in one fashion or another
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 wawait
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.źródło
Task.Run(() => MethodWithParameter(param));
. Co oznacza, że jeśliparam
zostanie zmodyfikowany po rozszerzeniuTask.Run
, możesz mieć nieoczekiwane wyniki naMethodWithParameter
.Użyj zmiennej przechwytywania, aby „przekazać” parametry.
var x = rawData; Task.Run(() => { // Do something with 'x' });
Możesz również użyć
rawData
bezpośrednio, ale musisz być ostrożny, jeśli zmienisz wartośćrawData
poza zadaniem (na przykład iterator wfor
pętli), zmieni to również wartość wewnątrz zadania.źródło
Task.Run
.Od teraz możesz również:
Action<int> action = (o) => Thread.Sleep(o); int param = 10; await new TaskFactory().StartNew(action, param)
źródło
IDisposable
obiekt do delegata zadania, aby rozwiązać ostrzeżenie ReSharper „Przechwycona zmienna jest umieszczana w zewnętrznym zasięgu” , robi to bardzo dobrze. Wbrew powszechnemu przekonaniu nie ma nic złego w używaniuTask.Factory.StartNew
zamiast tego,Task.Run
gdzie musisz przejść stan. Zobacz tutaj .Wiem, że to stary wątek, ale chciałem udostępnić rozwiązanie, z którego musiałem skorzystać, ponieważ zaakceptowany post nadal zawiera problem.
Problem:
Jak zauważył Alexandre Severino, jeśli
param
(w poniższej funkcji) zmieni się wkrótce po wywołaniu funkcji, możesz uzyskać nieoczekiwane zachowanie w programieMethodWithParameter
.Moje rozwiązanie:
Aby to wyjaśnić, napisałem coś bardziej podobnego do następującego wiersza kodu:
(new Func<T, Task>(async (p) => await Task.Run(() => MethodWithParam(p)))).Invoke(param);
Pozwoliło mi to bezpiecznie korzystać z parametru asynchronicznie, mimo że parametr zmieniał się bardzo szybko po uruchomieniu zadania (co powodowało problemy z opublikowanym rozwiązaniem).
Korzystając z tego podejścia,
param
(typ wartości) pobiera swoją wartość przekazaną, więc nawet jeśli metoda asynchroniczna jest uruchamiana poparam
zmianach,p
będzie miała dowolną wartość,param
jaką miał, gdy ten wiersz kodu został uruchomiony.źródło
var localParam = param; await Task.Run(() => MethodWithParam(localParam));
Po prostu użyj Task.Run
var task = Task.Run(() => { //this will already share scope with rawData, no need to use a placeholder });
Lub, jeśli chcesz użyć go w metodzie i poczekaj na zadanie później
public Task<T> SomethingAsync<T>() { var task = Task.Run(() => { //presumably do something which takes a few ms here //this will share scope with any passed parameters in the method return default(T); }); return task; }
źródło
for(int rawData = 0; rawData < 10; ++rawData) { Task.Run(() => { Console.WriteLine(rawData); } ) }
, nie będą zachowywać się tak samo, jak gdybyrawData
zostało przekazane, jak w przykładzie StartNew OP.Nie jest jasne, czy pierwotny problem był tym samym problemem, który miałem: chęć maksymalnego zwiększenia liczby wątków procesora podczas obliczeń wewnątrz pętli, przy jednoczesnym zachowaniu wartości iteratora i zachowaniu inline, aby uniknąć przekazywania tony zmiennych do funkcji roboczej.
for (int i = 0; i < 300; i++) { Task.Run(() => { var x = ComputeStuff(datavector, i); // value of i was incorrect var y = ComputeMoreStuff(x); // ... }); }
Mam to do pracy, zmieniając zewnętrzny iterator i lokalizując jego wartość za pomocą bramki.
for (int ii = 0; ii < 300; ii++) { System.Threading.CountdownEvent handoff = new System.Threading.CountdownEvent(1); Task.Run(() => { int i = ii; handoff.Signal(); var x = ComputeStuff(datavector, i); var y = ComputeMoreStuff(x); // ... }); handoff.Wait(); }
źródło
Pomysł polega na unikaniu używania sygnału jak powyżej. Pompowanie wartości int do struktury zapobiega zmianie tych wartości (w strukturze). Miałem następujący problem: zmienna pętla zmieniłabym się przed wywołaniem DoSomething (i) (i został zwiększony na końcu pętli przed wywołaniem () => DoSomething (i, i i)). Ze strukturami to się już nie zdarza. Paskudny błąd do znalezienia: DoSomething (i, i i) wygląda świetnie, ale nigdy nie jestem pewien, czy zostanie wywołany za każdym razem z inną wartością dla i (lub tylko 100 razy z i = 100), stąd -> struct
struct Job { public int P1; public int P2; } … for (int i = 0; i < 100; i++) { var job = new Job { P1 = i, P2 = i * i}; // structs immutable... Task.Run(() => DoSomething(job)); }
źródło