W Visual Studio 2012 zostały wprowadzone dwa nowe słowa kluczowe: async i await. Modyfikator async stwierdza, że metoda jest asynchroniczna, natomiast await służy do wywołania takiej metody. Drobnej modyfikacji wymaga też typ zwrotny, a dokładniej wymaga dodania typu Task.
Przed przedstawieniem pierwszego przykładu odpowiem jeszcze, po co stosować metody asynchroniczne? Odpowiedz jest prosta – ułatwiają one programowanie współbieżne/wielowątkowe.
Opisywany przykład opiera się na aplikacji ASP MVC. Porównałem w niej szybkość działania akcji synchronicznych i asynchronicznych. Aplikacja służy do pobierania temperatury dla zadanego miasta od trzech dostawców i obliczeniu średniej. Pierwszym krokiem jest stworzenie nowego projektu typu ASP MVC. Struktura gotowej aplikacji będzie wyglądała tak jak na poniższym rysunku:
Następnie należy utworzyć kontroler o nazwie WeatherController:
1 2 3 4 5 6 |
[UseStopwatch] [OutputCache(Location = OutputCacheLocation.None, NoStore = true)] public class WeatherController : Controller { } |
W przykładzie będziemy mierzyć czas wykonywania akcji. Dlatego należy zapobiec cachowaniu stron. Do tego celu służy ustawienie OutputCache. Dodano także atrybut UseStopwatchAttribute:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class UseStopwatchAttribute : System.Web.Mvc.ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); filterContext.Controller.ViewBag.stopWatch = stopWatch; } public override void OnResultExecuting(ResultExecutingContext filterContext) { Stopwatch stopWatch = (Stopwatch)filterContext.Controller.ViewBag.stopWatch; stopWatch.Stop(); double et = stopWatch.Elapsed.Seconds + (stopWatch.Elapsed.Milliseconds / 1000.0); filterContext.Controller.ViewBag.elapsedTime = et.ToString(); } } |
Atrybut służy do mierzenia czasu wykonywania akcji. Wykorzystuje on klasę diagnostyczną Stopwatch.
Pierwsza akcja będzie pobierała temperatury w sposób synchroniczny:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public ActionResult GetForecast(string city = "Tarnów") { ViewBag.MethodType = "Synchronous"; var service1 = new SourceService1(); var service2 = new SourceService2(); var service3 = new SourceService3(); var viewModel = new ForecastViewModel( service1.GetTemperatureByCity(city), service2.GetTemperatureByCity(city), service3.GetTemperatureByCity(city) ); return View("Forecast", viewModel); } |
Na potrzeby aplikacji stworzono trzy klasy które symulują pobieranie temperatur od zewnętrznych dostawców (SourceService1-3):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
public class SourceService1 { private readonly int Sleep = 3000; private readonly double ReturnTemperature = 24.1; public double GetTemperatureByCity(string city) { System.Threading.Thread.Sleep(Sleep); return ReturnTemperature; } public async Task<double> GetTemperatureByCityAsync(string city) { await Task.Delay(Sleep); return ReturnTemperature; } } public class SourceService2 { private readonly int Sleep = 2800; private readonly double ReturnTemperature = 23.5; public double GetTemperatureByCity(string city) { System.Threading.Thread.Sleep(Sleep); return ReturnTemperature; } public async Task<double> GetTemperatureByCityAsync(string city) { await Task.Delay(Sleep); return ReturnTemperature; } } public class SourceService3 { private readonly int Sleep = 3300; private readonly double ReturnTemperature = 24.1; public double GetTemperatureByCity(string city) { System.Threading.Thread.Sleep(Sleep); return ReturnTemperature; } public async Task<double> GetTemperatureByCityAsync(string city) { await Task.Delay(Sleep); return ReturnTemperature; } } |
Zawierają one metody: GetTemperatureByCity, GetTemperatureByCityAsync. Wykonują one to samo zadanie, z tą różnicą że jedna jest synchroniczna a druga asynchroniczna. Każda z metod jest zatrzymywana na okres około trzech sekund, ma to na celu symulowanie opóźnień od zewnętrznych dostawców temperatur.
Kolejną klasą jest ForecastViewModel:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class ForecastViewModel { [DisplayName("Temperature 1")] public double Temperature1 { get; set; } [DisplayName("Temperature 2")] public double Temperature2 { get; set; } [DisplayName("Temperature 3")] public double Temperature3 { get; set; } [DisplayName("Avg Temperature")] public double AvgTemperature { get { double[] array = { Temperature1, Temperature2, Temperature3 }; return array.Average(); } } public ForecastViewModel(double t1, double t2, double t3) { Temperature1 = t1; Temperature2 = t2; Temperature3 = t3; } } |
Jest to prosta klasa która przetrzymuje informacje o temperaturach. Ostatnim elementem jest widok Forecast.cshtml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
@model WeatherAsync.Models.ForecastViewModel @{ ViewBag.Title = "Forecast"; } <h2>Forecast</h2> <fieldset> <legend>Temperature</legend> <h4>Method Type: @ViewBag.MethodType</h4> <h4>Execution Time: @ViewBag.elapsedTime</h4> <div class="display-label"> @Html.DisplayNameFor(model => model.Temperature1) </div> <div class="display-field"> @Html.DisplayFor(model => model.Temperature1) </div> <div class="display-label"> @Html.DisplayNameFor(model => model.Temperature2) </div> <div class="display-field"> @Html.DisplayFor(model => model.Temperature2) </div> <div class="display-label"> @Html.DisplayNameFor(model => model.Temperature3) </div> <div class="display-field"> @Html.DisplayFor(model => model.Temperature3) </div> <div class="display-label"> @Html.DisplayNameFor(model => model.AvgTemperature) </div> <div class="display-field"> @Html.DisplayFor(model => model.AvgTemperature) </div> </fieldset> |
Wyświetla on informację: o typie akcji, czasie wykonania i temperaturach:
Jak widać akcja wykonała się w czasie 9,415 sekund. Ostatnim elementem będzie implementacja akcji asynchronicznej:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public async Task<ActionResult> GetForecastAsync(string city = "Tarnów") { ViewBag.MethodType = "Asynchronous"; var service1 = new SourceService1(); var service2 = new SourceService2(); var service3 = new SourceService3(); var serv1 = service1.GetTemperatureByCityAsync(city); var serv2 = service2.GetTemperatureByCityAsync(city); var serv3 = service3.GetTemperatureByCityAsync(city); await Task.WhenAll(serv1, serv2, serv3); var viewModel = new ForecastViewModel( serv1.Result, serv2.Result, serv3.Result ); return View("Forecast", viewModel); } |
Najciekawszym elementem w tej akcji jest await Task.WhenAll(serv1, serv2, serv3). Wszystkie trzy elementy są przetwarzane równolegle, przez co teoretycznie można w tym przypadku uzyskać trzy krotną redukcje potrzebnego czasu. A jak jest w praktyce? Bardzo dobrze:
Najnowsze komentarze