Jestem nowy w programowaniu asynchronicznym z async
modyfikatorem. Próbuję dowiedzieć się, jak upewnić się, że moja Main
metoda aplikacji konsolowej faktycznie działa asynchronicznie.
class Program
{
static void Main(string[] args)
{
Bootstrapper bs = new Bootstrapper();
var list = bs.GetList();
}
}
public class Bootstrapper {
public async Task<List<TvChannel>> GetList()
{
GetPrograms pro = new GetPrograms();
return await pro.DownloadTvChannels();
}
}
Wiem, że to nie działa asynchronicznie „od góry”. Ponieważ nie można określić async
modyfikatora Main
metody, jak mogę uruchomić kod w ramach main
asynchronicznie?
c#
.net
asynchronous
console-application
Danielovich
źródło
źródło
Odpowiedzi:
Jak odkryłeś, w VS11 kompilator nie zezwala na
async Main
metodę. Było to dozwolone (ale nigdy nie zalecane) w VS2010 z Async CTP.Mam najnowsze posty na blogu dotyczące asynchronizacji / oczekiwania i w szczególności asynchronicznych programów konsolowych . Oto kilka podstawowych informacji z postu wprowadzającego:
Oto dlaczego jest to problem w programach konsoli z
async Main
:Jednym z rozwiązań jest zapewnienie własnego kontekstu - „głównej pętli” dla programu konsoli, która jest kompatybilna z asynchronicznie.
Jeśli masz komputer z Async CTP, możesz korzystać
GeneralThreadAffineContext
z My Documents \ Microsoft Visual Studio Async CTP \ Samples (C # Testing) Testowanie jednostkowe \ AsyncTestUtilities . Alternatywnie możesz użyćAsyncContext
z mojego pakietu Nito.AsyncEx NuGet .Oto przykład użycia
AsyncContext
;GeneralThreadAffineContext
ma prawie identyczne zastosowanie:Alternatywnie możesz po prostu zablokować główny wątek konsoli, dopóki praca asynchroniczna nie zostanie zakończona:
Zwróć uwagę na użycie
GetAwaiter().GetResult()
; pozwala to uniknąćAggregateException
owijania, które występuje, jeśli używaszWait()
lubResult
.Aktualizacja, 30.11.2017: Od wersji 3 programu Visual Studio 2017 (15.3) język obsługuje teraz
async Main
- o ile zwracaTask
lubTask<T>
. Możesz teraz to zrobić:Semantyka wydaje się być taka sama, jak
GetAwaiter().GetResult()
styl blokowania głównego wątku. Jednak nie ma jeszcze specyfikacji języka dla C # 7.1, więc jest to tylko przypuszczenie.źródło
Wait
lubResult
, i nie ma nic złego. Należy jednak pamiętać, że istnieją dwie ważne różnice: 1) wszystkieasync
kontynuacje są uruchamiane w puli wątków, a nie w głównym wątku, oraz 2) wszelkie wyjątki są zawinięte w plikAggregateException
.<LangVersion>latest</LangVersion>
do pliku csproj, jak pokazano tutaj .Możesz rozwiązać ten problem za pomocą tej prostej konstrukcji:
Spowoduje to umieszczenie wszystkiego, co robisz w ThreadPool tam, gdzie chcesz (więc inne zadania, które uruchamiasz / czekasz, nie próbują ponownie dołączyć do wątku, których nie powinny) i poczekają, aż wszystko się skończy, zanim zamkniesz aplikację Console. Nie ma potrzeby stosowania specjalnych pętli ani zewnętrznych bibliotek.
Edycja: uwzględnij rozwiązanie Andrew dla nieprzechwyconych wyjątków.
źródło
Wait()
zGetAwaiter().GetResult()
was będzie uniknąćAggregateException
opakowanie kiedy rzucać rzeczy.async main
jest wprowadzane w C # 7.1, od tego pisania.Możesz to zrobić bez potrzeby korzystania z zewnętrznych bibliotek, wykonując następujące czynności:
źródło
getListTask.Result
jest to również wywołanie blokujące, więc powyższy kod może zostać napisany bezTask.WaitAll(getListTask)
.GetList
wyrzucisz, będziesz musiał złapaćAggregateException
i przesłuchać jego wyjątki, aby ustalić faktyczny zgłoszony wyjątek. Można jednak zadzwonićGetAwaiter()
, aby uzyskaćTaskAwaiter
dlaTask
i rozmowaGetResult()
na ten temat, tjvar list = getListTask.GetAwaiter().GetResult();
. Po uzyskaniu wyniku zTaskAwaiter
(również wywołania blokującego) wszelkie zgłoszone wyjątki nie zostaną zawinięte wAggregateException
.W C # 7.1 będziesz mógł wykonać właściwą asynchronizację Main . Odpowiednie podpisy dla
Main
metody zostały rozszerzone na:Na przykład możesz robić:
W czasie kompilacji metoda punktu wejścia asynchronicznego zostanie przetłumaczona na wywołanie
GetAwaitor().GetResult()
.Szczegóły: https://blogs.msdn.microsoft.com/mazhou/2017/05/30/c-7-series-part-2-async-main
EDYTOWAĆ:
Aby włączyć funkcje języka C # 7.1, należy kliknąć projekt prawym przyciskiem myszy i kliknąć „Właściwości”, a następnie przejść do zakładki „Kompilacja”. Tam kliknij przycisk zaawansowany u dołu:
Z menu rozwijanego wersji językowej wybierz „7.1” (lub dowolną wyższą wartość):
Domyślnie jest to „najnowsza wersja główna”, która w momencie pisania tego tekstu miałaby wartość C # 7.0, która nie obsługuje asynchronizacji głównej w aplikacjach konsolowych.
źródło
Dodam ważną funkcję, którą przeoczyły wszystkie pozostałe odpowiedzi: anulowanie.
Jedną z najważniejszych rzeczy w TPL jest obsługa anulowania, a aplikacje konsolowe mają wbudowaną metodę anulowania (CTRL + C). Bardzo łatwo jest je ze sobą połączyć. Oto jak ustrukturyzowałem wszystkie moje aplikacje asynchronicznej konsoli:
źródło
Wait()
również do?Wait()
, nie będzie czekać na zakończenie kodu asynchronicznego - przestanie czekać i natychmiast zakończy proces.Wait()
metoda zostanie przekazana z tym samym tokenem. Próbuję powiedzieć, że to nie wydaje się mieć żadnej różnicy.C # 7.1 (przy użyciu aktualizacji 3 vs 2017) wprowadza asynchroniczną wersję główną
Możesz pisać:
Aby uzyskać więcej informacji Seria C # 7, część 2: Asynchronizacja główna
Aktualizacja:
Może pojawić się błąd kompilacji:
Ten błąd wynika z faktu, że vs2017.3 jest skonfigurowany domyślnie jako c # 7.0, a nie c # 7.1.
Powinieneś jawnie zmodyfikować ustawienia swojego projektu, aby ustawić funkcje c # 7.1.
Możesz ustawić c # 7.1 na dwa sposoby:
Metoda 1: Korzystanie z okna ustawień projektu:
Metoda 2: Ręcznie zmodyfikuj PropertyGroup .csproj
Dodaj tę właściwość:
przykład:
źródło
Jeśli używasz C # 7.1 lub nowszej, przejdź do odpowiedzi nawfal i po prostu zmień typ zwracanej metody Main na
Task
lubTask<int>
. Jeśli nie jesteś:async Task MainAsync
jak powiedział Johan ..GetAwaiter().GetResult()
aby złapać bazowy wyjątek, jak powiedział do0g .CTRL+C
powinna natychmiast zakończyć proces. (Dzięki Binki !)OperationCancelledException
- zwróć odpowiedni kod błędu.Ostateczny kod wygląda następująco:
źródło
e.Cancel = true
jest bezwarunkowy.Nie potrzebowałem jeszcze tak dużo, ale kiedy użyłem aplikacji konsolowej do szybkich testów i wymagałem asynchronizacji, właśnie to rozwiązałem:
źródło
SynchronizationContext
powiązania z głównym wątkiem. Więc nie zakleszczy się, ponieważ nawet bezConfigureAwait(false)
, wszystkie kontynuacje zostaną wykonane w puli wątków.Do asynchronicznego wywoływania zadania z Main użyj
Task.Run () dla .NET 4.5
Task.Factory.StartNew () dla .NET 4.0 (może wymagać biblioteki Microsoft.Bcl.Async do asynchronizacji i oczekiwania na słowa kluczowe)
Szczegóły: http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
źródło
W Main spróbuj zmienić połączenie na GetList na:
źródło
Kiedy wprowadzono CTP C # 5, z pewnością można było oznaczyć Main jako
async
... chociaż generalnie nie był to dobry pomysł. Wydaje mi się, że zmieniło się to wraz z wydaniem VS 2013 i stało się błędem.Jeśli nie rozpoczniesz żadnych innych wątków na pierwszym planie , program zakończy działanie po
Main
zakończeniu, nawet jeśli rozpoczął pracę w tle.Co naprawdę próbujesz zrobić? Zauważ, że twoja
GetList()
metoda naprawdę nie musi być teraz asynchroniczna - dodaje dodatkową warstwę bez prawdziwego powodu. Jest to logicznie równoważne (ale bardziej skomplikowane niż):źródło
DownloadTvChannels()
powraca? Przypuszczalnie zwraca aTask<List<TvChannel>>
nie? Jeśli nie, jest mało prawdopodobne, że będziesz w stanie na to poczekać. (Możliwe, biorąc pod uwagę wzór awaiter, ale mało prawdopodobne.) Jeśli chodzi oMain
metody - to jeszcze musi być statyczna ... czy zastąpić tenstatic
modyfikator zasync
modyfikatora może?public static async void Main() {}
? Ale jeśliDownloadTvChannels()
już zwraca aTask<List<TvChannel>>
, prawdopodobnie jest już asynchroniczny - więc nie musisz dodawać kolejnej warstwy. Warto to dokładnie zrozumieć.Najnowsza wersja C # - C # 7.1 pozwala na tworzenie aplikacji konsoli asynchronicznej. Aby włączyć C # 7.1 w projekcie, musisz zaktualizować VS do co najmniej 15.3 i zmienić wersję C # na
C# 7.1
lubC# latest minor version
. Aby to zrobić, przejdź do Właściwości projektu -> Kompilacja -> Zaawansowane -> Wersja językowa.Następnie zadziała następujący kod:
źródło
W MSDN dokumentacja metody Task.Run (działanie) zawiera ten przykład, który pokazuje, jak uruchomić metodę asynchronicznie z
main
:Zwróć uwagę na to oświadczenie, które następuje za przykładem:
Tak więc, jeśli chcesz zamiast zadanie uruchomić w głównym wątku aplikacji, patrz odpowiedź przez @StephenCleary .
Jeśli chodzi o wątek, w którym działa zadanie, zwróć również uwagę na komentarz Stephena do jego odpowiedzi:
(Zobacz Obsługa wyjątków (Biblioteka zadań równoległych), aby dowiedzieć się, jak włączyć obsługę wyjątków w celu radzenia sobie z
AggregateException
.)Na koniec w witrynie MSDN z dokumentacji dotyczącej metody Task.Delay (TimeSpan) ten przykład pokazuje, jak uruchomić zadanie asynchroniczne, które zwraca wartość:
Zauważ, że zamiast przekazywania
delegate
doTask.Run
możesz przekazać funkcję lambda w następujący sposób:źródło
Aby uniknąć zawieszania się, gdy wywołujesz funkcję gdzieś na stosie wywołań, który próbuje ponownie dołączyć do bieżącego wątku (który utknął w oczekiwaniu), musisz wykonać następujące czynności:
(obsada jest wymagana tylko w celu rozwiązania niejednoznaczności)
źródło
W moim przypadku miałem listę zadań, które chciałem uruchomić asynchronicznie z mojej głównej metody, używam tego w produkcji od dłuższego czasu i działa dobrze.
źródło