Question MVC4 + async / wait + return response avant la fin de l'action


Dans mon application MVC4, je dois ajouter un contrôleur pour télécharger et traiter des fichiers volumineux. Immédiatement après le téléchargement du fichier, je dois commencer le traitement asynchrone de ce fichier et renvoyer la réponse au navigateur sans attendre la fin du traitement.

De toute évidence, je pourrais commencer un nouveau thread pour traiter le fichier manuellement, mais je me demande si je peux implémenter ce scénario en utilisant le mécanisme async / waiting introduit avec .net 4.5

Pour tester le concept, j'ai essayé quelque chose comme ceci:

public async Task<ActionResult> Test()
{
    TestAsync();
    return View("Test");
}

public async void TestAsync()
{
    await LongRunning();
}

private Task<int> LongRunning()
{
    return Task<int>.Factory.StartNew(() => Pause());
}

private int Pause()
{
    Thread.Sleep(10000);
    return 3;
}

Le mécanisme asynchrone semble fonctionner en général: lorsque je débogue le code, j'appuie sur la "vue retour" ("Test"); " ligne avant la ligne "retour 3". Cependant, le navigateur ne reçoit la réponse qu'une fois la méthode Pause terminée.

Cela semble se comporter comme des contrôleurs asynchrones classiques (ceux avec les méthodes Async et Completed). Y a-t-il un moyen d'utiliser async / waiting dans les contrôleurs pour mon scénario?


15
2017-09-07 14:01


origine


Réponses:


De toute évidence, je pourrais commencer un nouveau thread pour traiter le fichier manuellement, mais je me demande si je peux implémenter ce scénario en utilisant le mécanisme async / waiting introduit avec .net 4.5

Non, vous ne pouvez pas, parce que async ne change pas le protocole HTTP.

Svick et James ont déjà posté les réponses correctes sous forme de commentaires, que je reproduis ci-dessous par souci de commodité:

IIS peut recycler votre application à tout moment.

Si vous avez des choses à faire depuis longtemps, faites-les ailleurs. Le «typique» est une file d'attente persistante (MSMQ, Azure, RabbitMQ, etc.) avec autre chose (service Windows, programme exécutable par le planificateur de tâches, application utilisant Quartz.net, etc.) pour les traiter.

Pour résumer, HTTP vous donne une requête et une réponse (async - et toute autre chose - ne changera pas cela).

ASP.NET est conçu autour de requêtes HTTP (et fait des suppositions comme "s'il n'y a pas de requêtes en suspens, alors il est prudent d'arrêter ce site Web"). Toi pouvez lancer un nouveau sujet et garder votre téléchargement en mémoire (ce qui est le moyen le plus simple de le faire), mais il est fortement déconseillé.

Pour votre situation, je vous recommande de suivre la suggestion de James:

  • Une fois le téléchargement terminé, enregistrez le téléchargement sur un stockage persistant (par exemple, une file d'attente Azure) et renvoyez un "ticket" au navigateur.
  • Avoir un autre processus (par exemple, un rôle de travail Azure) traiter la file d'attente, éventuellement le marquer comme terminé.
  • Demandez au navigateur d'interroger le serveur avec son "ticket". Le serveur utilise le "ticket" pour rechercher le fichier dans un stockage persistant et retourner si oui ou non il est complet.

Il y a quelques variantes à cela (par exemple, utiliser SignalR pour notifier le navigateur lorsque le traitement est terminé), mais l'architecture générale est la même.

C'est complexe, mais c'est la bonne façon de le faire.


13
2017-09-07 18:45



Vous avez une erreur dans votre code. Vous devez attendre l'appel à TestAsync dans votre action. Si vous ne le faites pas, il retourne simplement un objet "Task" sans rien exécuter.

public async Task<ActionResult> Test()
{
    await TestAsync();
    return View("Test");
}

et la bonne façon de dormir dans une méthode asynchrone est d'appeler

await Task.Delay(10000);

Vous devez changer la signature de TestAsync: vous ne devez pas marquer une méthode asynchrone si elle retourne un void. Il doit retourner la tâche à la place. Renvoyer un vide est réservé à la compatibilité avec les événements .net.

public async Task TestAsync()
{
    await LongRunning();
}

Le corps de la méthode est le même.


1
2017-09-14 14:01



Votre méthode LongRunning dort de manière synchrone 10 secondes. Changez-le pour que le sommeil se produise dans la tâche à la place.


0
2017-09-07 14:15