MVC4 és az aszinkron action

Az előző cikkre reagálva egyik kedves olvasó megjegyezte, hogy az MVC4 Controller osztálya az előző lehetőségekhez képest jobb támogatást ad aszinkron műveletekre. Gyorsan utánanéztem ennek, és az MVC4-gyel valóban könnyebben leírhatók az előző cikk példái.

Mikor lehet küldeni a response-t?

Az aszinkron műveletek velejárója, hogy valahogy értesülni kell róla, ha az aszinkron művelet befejeződött. Az MVC3-ban erre szolgált az AsyncManager osztály, ami lényegében egy egyszerű számláló segítségével mondta meg, hogy mikor fejeződtek be az aszinkron műveletek. Ezt a számlálót a programozónak kell növelni egy aszinkron művelet indításánál, és csökkenteni, amikor az aszinkron művelet befejeződött. Amikor a számláló elérte a nullát, akkor kezdődött az action második felének (az xxxCompleted metódusnak) a hívása.

Jövőbeli értékek

A Task Parallel Library fejlesztése elején volt egy olyan koncepció, ami jövőben előálló értékeket írt le. A jövőbeli érték számítását elindíthatom most, aztán a számítás eredményét reprezentáló valamit vihetem tovább a programlogikában, még akkor is, ha az ismeretlen. Ennek a jövőbeli értéknek a kifejezésére a Future<T> osztály szolgált. Később ezt átnevezték Task<T>-re.

Elnevezéstől függetlenül, amit az MVC-nek tudnia kell kezelni, az a jövőben előálló ActionResult. Ennek megfelelően az MVC4 számlálók karbantartása helyett a jövőbeli értékek koncepcióját, azaz a Task<ActionResult>-t is támogatja, mint egy action visszatérési értéke.

A Task-nek köszönhetően így már nincs szükség számlálókra, az MVC4 akkor generál response-t, amikor a jövőbeli ActionResult konkretizálódott.

Await-tel megsózva

Az MVC4 új képességével, és az async/await kulcsszóval így nagyon szép action-okat írhatunk le. Nézzük például a következőt:

public class AlmaController : Controller
{
  ...

  private async Task<string> GetKorteAsync()
  {
      var start = DateTime.Now;
      await Task.Delay(2000);
      return FormatResult("Korte", start);
  }

  public async Task<ViewResult> Index()
  {
    ViewBag.Korte = await GetKorteAsync();
    return View();
  }
}

Mit ír le a logikai szál?

Bár az előző cikkben már kitértünk rá, érdemes újra megnézni a következő találós kérdést. Hogy fut le az alábbi, egyébként nagyon tiszta kód:

public async Task<ViewResult> Index()
{
    ViewBag.Korte = await GetKorteAsync();
    ViewBag.Barack = await GetBarackAsync();

    return View();
}

Ha a programot lefuttatjuk a következő eredményt kapjuk:

Az előző cikk alapján talán már nem nagy a meglepetés: az await mögötti implementáció a második aszinkron hívást csak akkor hajtja végre, ha az első már befejeződött. Bár mi a szemünkkel könnyen belátjuk, hogy a kettő hívás egyszerre indítható, valószínűleg elég bonyolult elemzés kellene ahhoz, hogy a compiler végül mindent párhuzamosítson, amit lehet. Én magam részéről nem is örülnék neki, esély sem lenne utána emberi ésszel kiszámítani, hogy mi történik a háttérben.

Ahhoz tehát, hogy több aszinkron hívás esetén is igazi aszinkronitást kapjunk, még mindig vissza kell térni a taszkokhoz:

public async Task<ViewResult> Index()
{
    await Task.Factory.ContinueWhenAll(
            new[] 
            { 
                GetBarackAsync(), 
                GetKorteAsync() 
            },
            results =>
            {
                ViewBag.Barack = results[0].Result;
                ViewBag.Korte = results[1].Result;
            });
            
    return View();
}

Ekkor az eredmény:

Nem kell többet AsyncController?

Legjobb, ha magyarázatként idemásolom az AsyncController forráskódját:

// Controller now supports asynchronous operations.
// This class only exists 
// a) for backwards compat for callers that derive from it,
// b) ActionMethodSelector can detect it to bind to ActionAsync/ActionCompleted patterns. 
public abstract class AsyncController : Controller
{
}

Végszó

Az MVC4-es észrevételt köszönöm, bárki tud hasonló hasznos kiegészítést, mindig szívesen veszem.

  1. #1 by tflamich on 2012. November 7. - 11:44

    Ez gyors volt🙂 Még egy megjegyzés: lehet áttekinthetőbben is párhuzamos futtatást implementálni, MS rendezvényeken ezt szokták súlykolni:

    public async Task Index()
    {
    var barack = GetBarackAsync();
    var korte = GetKorteAsync();

    await Task.WhenAll(barack, korte);

    ViewBag.Barack = barack Result;
    ViewBag.Korte = korte .Result;

    return View();
    }

    .NET 4.0 – ben nincsen WhenAll, de Async Targeting Pack-ot használva, ha minden igaz, a TaskEx osztályban van.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: