2013-08-21 23 views
8

Próbowałem przeczytać na temat metod asynchronicznych i teraz próbuję utworzyć własną metodę asynchroniczną. Metoda jest wywołaniem usługi WWW, które zwraca listę dzienników błędów. Nie jestem pewien, czy zrozumiałem poprawnie, więc pomyślałem, że podzielę się moim kodem, aby sprawdzić, czy powinienem zrobić coś innego.Tworzenie metody asynchronicznej usługi sieciowej

Wszystko, co chcę zrobić, to zwrócić listę błędów, wywołując metodę GetAllErrorLogs(), która jest metodą zsynchronizowaną. Ponieważ może zająć sekundę, aby pobrać wszystkie dzienniki błędów, chcę mieć możliwość zrobienia innych rzeczy, gdy zadzwoniłem do metody GetAllErrorLogs(). Oto kod.

[WebMethod] 
public async Task<List<ErrorLog>> GetAllErrorLogs() 
{ 
    List<ErrorLog> errorLogs = new List<ErrorLog>(); 

    await System.Threading.Tasks.Task.Run(() => { 
     errorLogs = ErrorLogRepository.GetAllErrorLogs(); 
    }); 


    if (errorLogs == null) 
     return new List<ErrorLog>(); 

    return errorLogs; 
} 

Dzięki!

+1

Nie widzę wiele korzyści korzystania asynchronicznie/czekają na stronie serwera. Używasz więcej wątków do tego samego. – I4V

+4

@ I4V: 'async' po stronie serwera może znacznie * zmniejszyć * liczbę wątków używanych na żądanie (zakładając, że kod jest naturalnie asynchroniczny, a nie fałszywy asynchroniczny, taki jak' Task.Run'). W rezultacie serwery asynchroniczne są w stanie skalować znacznie lepiej, często rzędu 10-100x. –

+0

Stephan Cleary ma rację ... Uczestniczyłem w kursie szkoleniowym na terenie kampusu microsoft, gdzie powiedziano nam, że po prostu asynchronizując cały kod uruchomiony na serwerze, uzyskasz ogromny wzrost wydajności tego samego sprzętu. Czysto, ponieważ gdy metoda oczekuje na odpowiedź z połączenia podrzędnego, główny wątek jest uruchamiany, aby przejść do innej pracy ... tj. Radzić sobie z innymi współbieżnymi żądaniami internetowymi. Zaletą jest wielozadaniowość. – GPR

Odpowiedz

7

Niedawno wygłosiłem wykład pod adresem ThatConference pod numerem async on the server side i rozwiązuję ten problem na slajdach.

Po stronie serwera należy unikać używania Task.Run i innych konstrukcji, które działają w kolejce do puli wątków. O ile to możliwe, pozostaw wątki puli wątków dostępne do obsługi żądań.

Idealnie więc twoje repozytorium miałoby asynchroniczną metodę GetAllErrorLogsAsync, która sama byłaby asynchroniczna. Jeśli GetAllErrorLogs nie może być asynchroniczny, możesz równie dobrze wywołać to bezpośrednio (usuwając await Task.Run).

Ponieważ pobranie wszystkich dzienników błędów może zająć sekundę, chcę mieć możliwość zrobienia innych rzeczy po wywołaniu metody GetAllErrorLogs().

Jeśli masz dostępne GetAllErrorLogsAsync, można to łatwo zrobić, używając Task.WhenAll. Jeśli jednak GetAllErrorLogs jest synchroniczna, możesz to zrobić tylko wykonując pracę równoległą w swoim żądaniu (np. Wiele połączeń z numerem Task.Run, a następnie Task.WhenAll).

Do równoległego kodu na serwerze należy podchodzić z wielkim niepokojem. Jest to dopuszczalne tylko w bardzo ograniczonym zestawie scenariuszy. Cały punkt async po stronie serwera polega na użyciu od mniejszej liczby wątków na żądanie, a gdy rozpoczniesz pracę równoległą, robisz odwrotnie: wiele wątków na żądanie. Jest to właściwe tylko wtedy, gdy wiesz, że twoja baza użytkowników jest bardzo mała; w przeciwnym razie zabijesz skalowalność serwera.

+0

Dzięki za odpowiedź! – Andreas

+0

Złożoności paralelizmu są tu zbyt długie i nie dotyczą tematu, ale należy zauważyć, że ostrzeżenie przed paralelizmem jest tu zbyt szerokie. Jeśli czekasz na żądanie sieciowe do usługi sieciowej i wywołania bazy danych, wykorzystujesz wątki portów I/O Completion Port, które są bardzo tanie, na poziomie systemu operacyjnego i nie zjadasz wątków z ThreadPool. Idealny scenariusz równoległości, który przyspiesza działanie użytkownika, nie powodując nalotu w aplikacji Request Threadpool. Odpowiednia odpowiedź brzmi: "Oto jak" nie "Nie rób tego". http://stackoverflow.com/a/539968/176877 –

+0

@ChrisMoschini: Wprowadzam rozróżnienie między * wielowątkowość/paralelizm * ('Task.Run',' Parallel', etc) i * asynchrony * ('async',' czekają, itd.), z których oba są [różnymi formami * współbieżności *] (https://pbs.twimg.com/media/B63AADfIgAA4jPH.jpg:large). Z tymi definicjami równoległość jest zła po stronie serwera. –

0

** Jest to potencjalnie źle, czytać komentarze lub Spinoff pytanie na HttpContext.Current after an await

If ErrorLogRepository.GetAllErrorLogs() nie jest gwint -safe, spowoduje to dziwne błędy i potencjalnie wyjątek. Upewnij się, że Twój kod jest gotowy do pracy wielowątkowej przed przełączeniem na metody asynchroniczne, jest to oczywiście bardzo banalna porada, ale często pomijana. Na przykład, jeśli odwołasz się do HttpContext.Current w swoich metodach, twój kod zginie w metodzie asynchronicznej, a czasami nawet PO await. Powodem jest to, że kod w bloku asynchronicznym może być uruchamiany w osobnym wątku, który nie będzie miał dostępu do tej samej właściwości statycznej wątku, a await zostanie skompilowany na dwie metody. Cały kod przed numerem await jest uruchamiany w jednym wątku, a następnie wywołuje kod po oczekiwaniu na słowo kluczowe jako kontynuację, ale potencjalnie w jeszcze innym wątku. Tak więc czasami twój kod będzie działał nawet w bloku asynchronicznym, tylko po to, aby dusić się niespodziewanie po tym, jak zostanie "wyprowadzony" z asynchronizmu z powrotem do tego, co uważasz za synchroniczną część twojego kodu (ale w rzeczywistości wszystko po słowie kluczowym await nie jest już gwarantowane być oryginalnym wątkiem).

+0

W tej odpowiedzi jest wiele dezinformacji. 'UnobservedTaskException' nie jest tu wymagany (' await' poprawnie propaguje wszelkie wyjątki z 'GetAllErrorLogs' przez metodę WebAPI). 'HttpContext.Current' jest poprawnie propagowany do wszystkich wątków, które obsługują żądanie asynchroniczne, domyślnie (tzn. Wiersz' return errorLogs' ma całkowicie poprawny 'HttpContext.Current'). –

+0

Masz rację, oczekując, źle odczytałem tę część. Nie byłoby to możliwe tylko w przypadku zadania. Uruchomiony jako ogień i zapomniany. Jednak HttpContext.Current na pewno nie będzie ważny po oczekiwaniu, chyba że WebAPI przesłonił domyślne zachowanie kontekstu synchronizacji. – welegan

+1

WebAPI nie zapewnia kontekstu synchronizacji, ale działa w środowisku ASP.NET. Tak więc 'HttpContext.Current' jest całkowicie poprawny po 'aait'. –

0

Oto kod produkcji ...

using System.Web.Http; 
using AysncTask = System.Threading.Tasks.Task; 

public class myController : ApiControllerBase 
{ 
     [HttpPut] 
     [Route("api/cleardata/{id}/{requestId}/")] 
     public async AysncTask ClearData(Guid id, Guid requestId) 
     { 
      try 
      { 
       await AysncTask.Run(() => DoClearData(id, requestId)); 
      } 
      catch (Exception ex) 
      { 
       throw new Exception("Exception in myController.ClearData", ex); 
      } 
     } 
} 
0

Obsługa wyjątków ASYNC jest również bardzo ważne .. choć jest to dla app konsoli okna, te same zasady powinny mieć zastosowanie.

źródło: https://blogs.msdn.microsoft.com/ptorr/2014/12/10/async-exceptions-in-c/

using System; 
    using System.Runtime.CompilerServices; 
    using System.Threading; 
    using System.Threading.Tasks; 

    namespace AsyncAndExceptions 
    { 
class Program 
{ 
    static void Main(string[] args) 
    { 
    AppDomain.CurrentDomain.UnhandledException += (s, e) => Log("*** Crash! ***", "UnhandledException"); 
    TaskScheduler.UnobservedTaskException += (s, e) => Log("*** Crash! ***", "UnobservedTaskException"); 

    RunTests(); 

    // Let async tasks complete... 
    Thread.Sleep(500); 
    GC.Collect(3, GCCollectionMode.Forced, true); 
    } 

    private static async Task RunTests() 
    { 
    try 
    { 
     // crash 
     // _1_VoidNoWait(); 

     // crash 
     // _2_AsyncVoidAwait(); 

     // OK 
     // _3_AsyncVoidAwaitWithTry(); 

     // crash - no await 
     // _4_TaskNoWait(); 

     // crash - no await 
     // _5_TaskAwait(); 

     // OK 
     // await _4_TaskNoWait(); 

     // OK 
     // await _5_TaskAwait(); 
    } 
    catch (Exception ex) { Log("Exception handled OK"); } 

    // crash - no try 
    // await _4_TaskNoWait(); 

    // crash - no try 
    // await _5_TaskAwait(); 
    } 

    // Unsafe 
    static void _1_VoidNoWait() 
    { 
    ThrowAsync(); 
    } 

    // Unsafe 
    static async void _2_AsyncVoidAwait() 
    { 
    await ThrowAsync(); 
    } 

    // Safe 
    static async void _3_AsyncVoidAwaitWithTry() 
    { 
    try { await ThrowAsync(); } 
    catch (Exception ex) { Log("Exception handled OK"); } 
    } 

    // Safe only if caller uses await (or Result) inside a try 
    static Task _4_TaskNoWait() 
    { 
    return ThrowAsync(); 
    } 

    // Safe only if caller uses await (or Result) inside a try 
    static async Task _5_TaskAwait() 
    { 
    await ThrowAsync(); 
    } 

    // Helper that sets an exception asnychronously 
    static Task ThrowAsync() 
    { 
    TaskCompletionSource tcs = new TaskCompletionSource(); 
    ThreadPool.QueueUserWorkItem(_ => tcs.SetException(new Exception("ThrowAsync"))); 
    return tcs.Task; 
    } 
    internal static void Log(string message, [CallerMemberName] string caller = "") 
    { 
    Console.WriteLine("{0}: {1}", caller, message); 
    } 
} 

}

Powiązane problemy