2015-05-26 5 views
5

Może ktoś wyjaśnić ten przykład, który oczywiście nie działa:metoda C# asynchroniczny dzwonić przez całą drogę do Main

class Program 
{ 
    static void Main(string[] args)//main cant' be async 
    { 
     int res = test();//I must put await here 

     Console.WriteLine("END"); 
    } 

    public async static Task<int> test() 
    { //why can't I make it just: public int test()?? 
     int a1, a2, a3, a4; 

     a1 = await GetNumber1(); 
     a2 = await GetNumber2(); 
     a3 = await GetNumber3(); 

     a4 = a1 + a2 + a3; 
     return a4; 
    } 

    public static async Task<int> GetNumber1() 
    { 
     await Task.Run(() => 
      { 
       for (int i = 0; i < 10; i++) 
       { 
        Console.WriteLine("GetNumber1"); 
        System.Threading.Thread.Sleep(100); 
       } 
      }); 
     return 1; 
    } 

Staram się „zebrać” wartości z metod GenNumberX przy użyciu „czekają”. Chciałbym, aby metoda "testowa" nie była asynchroniczna. Nie rozumiem, dlaczego test musi być asynchroniczny, gdy używam czekać, aby uzyskać wartość. Ten przykład sprawia, że ​​piszę asynchronicznie na każdej metodzie, w której używam asynchronizacji, a kiedy wykonuję drążenie do Main, nie mogę tego zrobić asynchronicznie?

Jak napisać świecie rzeczywistym przykład:

public bool ReserveAHolliday() 
{ 
     bool hotelOK = await ReserveAHotel();//HTTP async request 
     bool flightOK = await ReserveFlight();////HTTP async request 
     bool result = hotelOK && flightOK; 
     return result; 
} 

Jak zrobić metoda ReserveAHolliday synchroniczny? Czy brakuje mi czegoś lub nie rozumiem mechanizmu async-aait?

+1

W pewnym momencie konieczne będzie uruchomienie asynktaksu z metody synchronicznej. Jeśli chcesz strzelać, po prostu wywołaj go (zwane również async voids, fire i forget) lub użyj 'test(). Czekaj()', który będzie czekał na zadanie synchronicznie. – FrankerZ

Odpowiedz

2

poniżej jest pełnym przykładem. możesz uruchomić ReserverAHoliday zarówno Synchronicznie (bool r = ReserveAHolliday(). Wynik;) i Asynchronicznie (po prostu wywołaj ReserveAHolliday();) z MAIN (zależy od tego, którą linię komentujesz). i możesz zobaczyć efekt ("END" zostanie wydrukowany przed/po zakończeniu rezerwacji). Preferuję oczekujące metody Task.WhenAll(), która jest bardziej czytelna. również zwrócić uwagę, że najlepiej jest oczekiwać na Task.Delay (100) zamiast Thread.sleep wewnątrz GetNumber1.

class Program 
{ 
    static void Main(string[] args)//main cant' be async 
    { 
     //int res = test().Result;//I must put await here 
     bool r = ReserveAHolliday().Result; //this will run Synchronously. 
     //ReserveAHolliday(); //this option will run aync : you will see "END" printed before the reservation is complete. 
     Console.WriteLine("END"); 
     Console.ReadLine(); 
    } 

    public async static Task<int> test() 
    { //why can't I make it just: public int test()?? 
     //becuase you cannot use await in synchronous methods. 
     int a1, a2, a3, a4; 

     a1 = await GetNumber1(); 
     a2 = await GetNumber1(); 
     a3 = await GetNumber1(); 

     a4 = a1 + a2 + a3; 
     return a4; 
    } 

    public static async Task<int> GetNumber1() 
    { 
     //await Task.Run(() => 
     // { 
       for (int i = 0; i < 10; i++) 
       { 
        Console.WriteLine("GetNumber1"); 
        await Task.Delay(100); // from what I read using Task.Delay is preferred to using System.Threading.Thread.Sleep(100); 
       } 
     // }); 
     return 1; 
    } 

    public async static Task<bool> ReserveAHolliday() 
    { 
     //bool hotelOK = await ReserveAHotel();//HTTP async request 
     //bool flightOK = await ReserveAHotel();////HTTP async request 
     var t1 = ReserveAHotel("FirstHotel"); 
     var t2 = ReserveAHotel("SecondHotel"); 
     await Task.WhenAll(t1, t2); 
     bool result = t1.Result && t1.Result;// hotelOK && flightOK; 
     return result; 
    } 
    public static async Task<bool> ReserveAHotel(string name) 
    { 
     Console.WriteLine("Reserve A Hotel started for "+ name); 
     await Task.Delay(3000); 
     if (name == "FirstHotel") 
      await Task.Delay(500); //delaying first hotel on purpose. 
     Console.WriteLine("Reserve A Hotel done for " + name); 
     return true; 
    } 
} 
+1

Dzięki Guy. Czy ten przykład może spowodować zakleszczenia, takie jak @Kabbalah wymienione w powyższej odpowiedzi? Prawdopodobnie powinienem zachować te metody asynchronicznie, ponieważ mam zamiar napisać bibliotekę do wykorzystania w projekcie WinForms. Ten kod to tylko przykład. Właściwie piszę klienta/wrapper SignalR, aby umożliwić komunikację między różnymi programami (ERP-> POS, POS-> ERP) z takimi metodami: _IsUserOnline (string userId) _, _GetOnlineUsers_, itp. – vpetrovic

+0

W WinForms, prawdopodobnie ... to zależy o tym, co dokładnie dzieje się w twojej bibliotece. Czy twój kod biblioteki wymaga kontekstu synchronizacji? Jeśli odpowiedź brzmi "tak", to na pewno będzie to impas.Jeśli odpowiedź brzmi "nie", możesz uciec, jeśli podasz ConfigureAwait (false) w swoim kodzie biblioteki. Zmienię moją odpowiedź, podając więcej informacji. – Kabbalah

+0

Proponuję spędzać czas na ten temat, aby nie zostać spalonym. Myślę, że jeśli będziesz czekać, będziesz bezpieczniejszy, ponieważ znajdujesz się w kontekście asynchronicznym, a kod synchroniczny, który początkowo nazywany jest metodą asynchroniczną, może kontynuować pracę ... , aby być bardziej pewnym implikacji. Sugeruję dodanie dużego opóźnienia wewnątrz asynchronizacji, dzięki czemu możesz zobaczyć wpływ zachowania dla siebie. również wolę uruchamianie takich async od właściwości bool (propfull), której używam jako flagi (tak, że to samo zadanie nie jest uruchamiane wiele razy równolegle, kiedy nie chcę tego). Nie wiem, czy to dotyczy ciebie i czy to dobra praktyka. – Guy

1

Twój asynchroniczny musi gdzieś zacząć, a ponieważ twój program ma punkt wyjścia, jest to metoda synchroniczna w tym momencie. Większość początków async zaczyna się od tego, co lubimy nazywać async void, które w zasadzie są metodami opartymi na pożarze i zapomnieniach. Rozpoczyna zadanie i nie obchodzi go, co powróci. Jeśli musisz czekać na coś metodą synchroniczną, możesz użyć metody .Wait() dla zadania lub użyć .Result, aby uzyskać wynik zadania.

Dla przykładu rzeczywistym świecie, jeśli chcesz uruchomić zarówno zadań w tym samym czasie, trzeba by zrobić coś takiego:

public async Task<bool> ReserveAHoliday() 
{ 
     //Initialize and start this task 
     Task<bool> hotelTask = ReserveAHotel();//HTTP async request 
     //Initialize and start this task 
     Task<bool> flightTask = ReserveFlight();////HTTP async request 
     //Await until hotel task is done, and get the bool 
     bool hotelOK = await hotelTask; 
     //Await until flight task is done (This might have already finished while the hotel was grabbing, and get the bool 
     bool flightOK = await flightTask; 
     bool result = hotelOK && flightOK; 
     //Return the final result 
     return result; 
} 

Gorąco polecam oglądanie this video. Daje to miłe wprowadzenie na temat działania Async i umożliwia rozpoczęcie pracy w cudownym świecie Async.

+0

_ReserveAHolliday_ nie będzie działać z powodu użycia _await_ bez deklarowania metody asynchronicznej. Tak więc wydaje się, że muszę używać metody _Task.Wait() _. – vpetrovic

+0

Zaktualizowałem przykład. ReserveAHoliday powinno być zadaniem asynchronicznym. Jak już wspomniałem, bardzo polecam sprawdzenie wideo, które połączyłem. To zajmie trochę czasu, ale jest to tego warte. – FrankerZ

0

Dziękuję FrankerZ za właściwą odpowiedź! Więc mój litle przykład powinien wyglądać mniej więcej tak:

class Program 
{ 
    static void Main(string[] args) 
    { 
     var task = test(); 
     task.Wait(); //this stops async behaviour 
     int result = task.Result;// get return value form method "test" 
     Console.WriteLine("RESULT IS = " + result); 
    } 

    public async static Task<int> test() 
    { 
     int a1, a2, a3, a4; 

     //run all tasks 
     //all tasks are doing their job "at the same time" 
     var taskA1 = GetNumber1(); 
     var taskA2 = GetNumber2(); 
     var taskA3 = GetNumber3(); 

     //wait for task results 
     //here I am collecting results from all tasks 
     a1 = await taskA1; 
     a2 = await taskA2; 
     a3 = await taskA3; 

     //get value from all task results 
     a4 = a1 + a2 + a3; 
     return a4; 
    } 

    public static async Task<int> GetNumber1() 
    { 
     await Task.Run(() => 
      { 
       for (int i = 0; i < 10; i++) 
       { 
        Console.WriteLine("GetNumber1"); 
        System.Threading.Thread.Sleep(100); 
       } 
      }); 
     return 1; 
    } 

// inne metody, pominięte, ponieważ są one takie same jak GetNumber1 }

+1

'Task.Result' blokuje do zakończenia zadania, więc nie potrzebujesz' task.Wait() ' –

+0

Uważaj, ponieważ działa to tylko w aplikacji konsolowej. Jeśli spróbujesz tego w aplikacji WPF lub Forms, najprawdopodobniej spowoduje to zakleszczenie. Zobacz link w moim poście na temat tego problemu. – Kabbalah

+0

Nie używaj 'Thread.Sleep (100)' - zamiast tego użyj 'await Task.Delay (100)'. –

0

Aby użyć słowa kluczowego await wewnątrz metody, ma być async. To była decyzja projektowa, aby ułatwić kompatybilność wsteczną.

Main nie może być async ponieważ jest punktem początkowym programu, więc zazwyczaj trzeba jakoś zablokować, aby program działał. Możesz to zrobić blokując na Task lub nawet dzwoniąc pod numer Console.ReadLine.

Zadania zwykle wykonywane na wątku w tle i wątkach w tle nie powodują działania programu, jeśli wszystkie wątki pierwszoplanowe zatrzymają się.

Mam wstępne blogu na async-awaithere

0

Metoda ReserveAHolliday nie mogą być wykonane synchronicznie jeśli używasz słowa kluczowego czekają wewnątrz niego.

To, co może być możliwe, to po prostu zablokować, aż obie metody asynchroniczne zwrócą wartość. Nie polecam go jednak, ponieważ może to prowadzić do zakleszczeń lub innych nieoczekiwanych błędów.

Jeśli nie masz kontekst synchronizacji (zastosowanie np. Konsola) można po prostu napisać

bool hotelOK = ReserveAHotel().Result; 

Ale liczyć się ostatecznie chcemy stworzyć GUI. Tam właśnie blokuje się blokowanie. Ponieważ aplikacja interfejsu użytkownika (formularze i WPF) mają kontekst i oczekiwanie na metody asynchroniczne powoduje zakleszczenia. Read more here

Istnieją obejścia tego problemu, ale są silnie uzależnione od konstrukcji aplikacji.

Edit:

Jeśli kod biblioteka potrzebuje kontekstu synchronizacji następnie blokuje z pewnością przyczyną impasu.

Jeśli nie jest to potrzebne, to można użyć ConfigureAwait(false) w kodzie biblioteki i uciec z blokowaniem.

Co mogę polecić to: asynchroniczny/czekają na wszystkich sposób asynchroniczny lub nie/czeka na wszystkich. Istnieją inne możliwości, aby twój kod był asynchroniczny, np .: oparty na zdarzeniach, ale jak wszystko w życiu ma zalety i wady.

Proszę przeczytać this blog z Stephen Cleary. Wyjaśnia, dlaczego blokowanie kodu asynchronicznego zakleszcza się i jak można tego uniknąć.

+0

Tak, piszę projekt biblioteki, który będzie używany w GUI WinForm. Jeśli to może sprawić pewne problemy, zachowam te metody _async_, to nie jest duży problem, chciałem zaprojektować te klasy w sposób jaki zaplanowałem, ale ten plan może być błędny. – vpetrovic