2016-08-03 33 views
8

Przeszukałem sieć i widziałem wiele pytań dotyczących task.run i czekam na asynchroniczne, ale istnieje taki scenariusz użycia, w którym nie bardzo rozumiem różnicę. Scenariusz jest dość prosty, wierzę.Czekajcie na task.run vs poczekajcie C#

await Task.Run(() => LongProcess()); 

vs

await LongProcess()); 

gdzie LongProcess jest sposób asynchroniczny z kilkoma asynchronicznych połączeń w tym takich jak połączenia czeka ExecuteReaderAsync db z (na przykład).

Pytanie:

Czy jest jakaś różnica między nimi w tym scenariuszu? Każda pomoc lub wkład doceniany, dzięki!

+0

'Task.Run' pozwala oczekiwać na metodę synchroniczną. Jeśli twoja metoda jest już asynchroniczna i zwraca zadanie, nie potrzebujesz tego. –

+0

Dwa fragmenty są * nie * równoważne. Pierwsza linia nie czeka na zakończenie funkcji 'LongProcess()', po prostu wywoła zadanie potomne i powróci. Aby były one równoważne, musisz użyć 'await LongProcess()' –

+3

@PanagiotisKanavos Not true. Kod dokładnie taki, jaki został opublikowany, użyje przeciążenia 'Func ' 'Task.Run', więc * będzie * czekało na zakończenie' LongProcess'. – Luaan

Odpowiedz

8

Task.Runmoże wysłać operację do przetworzenia w innym wątku. To jedyna różnica.

Może to być przydatne - na przykład, jeśli LongProcess nie jest naprawdę asynchroniczny, spowoduje to, że dzwoniący powróci szybciej. Ale dla prawdziwie asynchronicznej metody nie ma sensu używanie Task.Run, a to może powodować niepotrzebne marnowanie.

Należy jednak zachować ostrożność, ponieważ zachowanie Task.Run zmieni się w zależności od rozdzielczości przeciążenia. W twoim przykładzie zostanie wybrane przeciążenie Func<Task>, które (poprawnie) zaczeka na zakończenie LongProcess. Jeśli jednak został użyty delegat, który nie powrócił, Task.Run czeka tylko na wykonanie do pierwszej await (zauważ, że tak zachowuje się zawsze TaskFactory.StartNew, więc nie używaj tego).

+0

W tym przypadku rzeczywistą różnicą jest to, że w pierwszym fragmencie kod synchroniczny na początku 'LongProcess' będzie (może) działać w osobnym wątku. W drugim przypadku na pewno zadziała na wątku dzwoniącego –

8

Dość często ludzie sądzą, że oczekiwanie jest dokonywane przez kilka wątków. W rzeczywistości wszystko odbywa się za pomocą jednego wątku.

Rzeczą, która bardzo mi pomogła zrozumieć async-await, jest this interview with Eric Lippert about async-await. Gdzieś w środku porównuje async z kucharzem, który musi poczekać, aż się zagotuje. Zamiast nic nie robić, rozgląda się, czy nie jest jeszcze coś do zrobienia, jak krojenie cebuli. Jeśli to się skończy, a woda nadal się nie zagotuje, sprawdza, czy jest coś innego do zrobienia, i tak dalej, dopóki nie będzie miał nic do roboty poza czekaniem. W takim przypadku wraca do pierwszej rzeczy, na którą czekał.

Jeśli twoja procedura wywoła daną funkcję, jesteśmy pewni, że gdzieś w tej nieoczekiwanej funkcji jest wezwanie do funkcji, której nie można oczekiwać, w przeciwnym razie funkcja nie byłaby oczekiwana. W rzeczywistości twój kompilator ostrzeże cię, jeśli zapomnisz poczekać gdzieś w swojej oczekiwanej funkcji.

Jeśli twoja funkcja wywoła inną, oczekiwaną funkcję, wątek wchodzi do tej innej funkcji i zaczyna robić rzeczy w tej funkcji i przechodzi w inne funkcje, dopóki nie spotka się z oczekiwaniem.

Zamiast czekać na wyniki, wątek idzie w górę w stosie wywołań, aby sprawdzić, czy istnieją inne fragmenty kodu, które może przetworzyć, dopóki nie poczeka. Podejdź jeszcze raz do stosu wywołań, przetwórz, aż poczekasz, itd. Gdy wszyscy oczekują, wątek oczekuje na dno i poczekaj, aż to się skończy.

Ma to tę zaletę, że jeśli osoba wywołująca twoją nieoczekiwaną funkcję nie potrzebuje rezultatu twojej funkcji, ale może robić inne rzeczy zanim wynik będzie potrzebny, te inne rzeczy mogą być wykonane przez wątek zamiast czekania wewnątrz twoja funkcja.

Wezwanie natychmiast bez czekania na wynik będzie wyglądać następująco:

private async Task MyFunction() 
{ 
    Task<ReturnType>taskA = SomeFunctionAsync(...) 
    // I don't need the result yet, I can do something else 
    DoSomethingElse(); 

    // now I need the result of SomeFunctionAsync, await for it: 
    ReturnType result = await TaskA; 
    // now you can use object result 
} 

Należy zauważyć, że w tym scenariuszu wszystko odbywa się przez jeden wątek. Dopóki twoja nić ma coś do zrobienia, będzie zajęty.

Dodawanie. To nieprawda, że ​​zaangażowany jest tylko jeden wątek. Każdy wątek, który nie ma nic do roboty, może kontynuować przetwarzanie kodu po oczekiwaniu. Jeśli zaznaczysz identyfikator wątku, zobaczysz, że ten identyfikator można zmienić po oczekiwaniu. Kontynuowany wątek ma ten sam numer context, co oryginalny wątek, więc możesz działać tak, jakby był to oryginalny wątek. Nie trzeba sprawdzać pod kątem InvokeRequired, nie trzeba używać muteksów lub sekcji krytycznych. Dla twojego kodu jest to tak, jakby w grę wchodził jeden wątek.

Link do artykułu w końcu tej odpowiedzi wyjaśniono nieco więcej o kontekście wątku

zobaczysz awaitable funkcje głównie tam, gdzie jakiś inny proces musi robić rzeczy, podczas gdy Twój wątek prostu musi poczekaj, aż skończy się druga rzecz, np. wysyłając dane przez Internet, zapisując plik, komunikując się z bazą danych itp.

Jednak czasami trzeba wykonać ciężkie obliczenia i chcesz, aby twój wątek był wolny coś innego, na przykład reagowanie na dane wprowadzone przez użytkownika. W tej beczce możesz rozpocząć akcję, tak jakbyś zadzwonił do funkcji asynchronicznej.

Task<ResultType> LetSomeoneDoHeavyCalculations(...) 
{ 
    DoSomePreparations() 
    // start a different thread that does the heavy calculations: 
    var myTask = Task.Run(() => DoHeavyCalculations(...)) 
    // now you are free to do other things 
    DoSomethingElse(); 
    // once you need the result of the HeavyCalculations await for it 
    var myResult = await myTask; 
    // use myResult 
    ... 
} 

Teraz inny wątek wykonuje ciężkie obliczenia, podczas gdy twój wątek może wykonywać inne czynności. Gdy zacznie się czekać, twój rozmówca może robić rzeczy, dopóki nie zacznie czekać. W efekcie twoja nić będzie dość swobodnie reagować na dane wprowadzone przez użytkownika. Jednak będzie tak tylko w przypadku, gdy wszyscy będą czekać. Podczas gdy twój wątek jest zajęty robieniem rzeczy, których wątek nie może reagować na dane wprowadzone przez użytkownika. Dlatego zawsze upewnij się, że jeśli uważasz, że wątek UI musi zrobić trochę zajęty przetwarzanie że zajmuje trochę czasu korzystania Task.Run i niech inny wątek to zrobić

Kolejny artykuł, który pomógł mi: Async-Await by the brilliant explainer Stephen Cleary

+0

Dzięki temu naprawdę dobre wejścia! – mattias