web-dev-qa-db-fra.com

Exécution d'une opération asynchrone déclenchée par une demande de page Web ASP.NET

J'ai une opération asynchrone qui, pour diverses raisons, doit être déclenchée à l'aide d'un appel HTTP vers une page Web ASP.NET. Lorsque ma page est demandée, elle doit démarrer cette opération et retourner immédiatement un accusé de réception au client.

Cette méthode est également exposée via un service Web WCF, et elle fonctionne parfaitement.

Lors de ma première tentative, une exception a été levée, me disant:

Les opérations asynchrones ne sont pas autorisées dans ce contexte. 
 La page qui démarre une opération asynchrone doit avoir l'attribut Async 
 Défini sur true et une opération asynchrone ne peut être lancée que 
 Sur une page antérieure à l'événement PreRenderComplete.

Alors bien sûr, j'ai ajouté le Async="true" paramètre au @Page directive. Maintenant, je ne reçois pas d'erreur, mais la page se bloque jusqu'à la fin de l'opération asynchrone.

Comment puis-je faire fonctionner une vraie page à tirer et oublier?

Edit: Un peu de code pour plus d'informations. C'est un peu plus compliqué que cela, mais j'ai essayé d'avoir une idée générale là-dedans.

public partial class SendMessagePage : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        string message = Request.QueryString["Message"];
        string clientId = Request.QueryString["ClientId"];

        AsyncMessageSender sender = new AsyncMessageSender(clientId, message);
        sender.Start();

        Response.Write("Success");
    }
}

La classe AsyncMessageSender:

public class AsyncMessageSender
{
    private BackgroundWorker backgroundWorker;
    private string client;
    private string msg;

    public AsyncMessageSender(string clientId, string message)
    {
        this.client = clientId;
        this.msg = message;

        // setup background thread to listen
        backgroundThread = new BackgroundWorker();
        backgroundThread.WorkerSupportsCancellation = true;
        backgroundThread.DoWork += new DoWorkEventHandler(backgroundThread_DoWork);
    }

    public void Start()
    {
        backgroundThread.RunWorkerAsync();
    }

    ...
    // after that it's pretty predictable
}
53
Damovisa

Si vous ne vous souciez pas de renvoyer quoi que ce soit à l'utilisateur, vous pouvez simplement lancer un thread séparé, ou pour une approche rapide et sale, utilisez un délégué et invoquez-le de manière asynchrone. Si vous ne vous souciez pas d'avertir l'utilisateur lorsque la tâche asynchrone se termine, vous pouvez ignorer le rappel. Essayez de mettre un point d'arrêt à la fin de la méthode SomeVeryLongAction (), et vous verrez qu'il finit de s'exécuter après que la page a déjà été servie:

private delegate void DoStuff(); //delegate for the action

protected void Page_Load(object sender, EventArgs e)
{

}

protected void Button1_Click(object sender, EventArgs e)
{
    //create the delegate
    DoStuff myAction = new DoStuff(SomeVeryLongAction); 
    //invoke it asynchrnously, control passes to next statement
    myAction.BeginInvoke(null, null);
    Button1.Text = DateTime.Now.ToString();
}


private void SomeVeryLongAction()
{
    for (int i = 0; i < 100; i++)
    {
        //simulation of some VERY long job
        System.Threading.Thread.Sleep(100);
    }
}
31
Ilya Tchivilev

Si vous exécutez des formulaires Web, définissez Ansync = "true" dans votre page .aspx où vous effectuez la demande.
<%@ Page Language="C#" Async="true" ... %>

30
vikingben

OK, voici le problème: l'attribut Async est pour le cas où votre page va appeler une tâche de longue durée qui bloque également le thread, puis votre page a besoin de la sortie de cette tâche afin de renvoyer des informations à l'utilisateur. Par exemple, si votre page devait appeler un service Web, attendez sa réponse, puis utilisez les données de la réponse pour afficher votre page.

La raison pour laquelle vous utiliseriez l'attribut Async est d'éviter de bloquer le thread. Ceci est important car les applications ASP.NET utilisent un pool de threads pour répondre aux demandes et il n'y a qu'un nombre relativement petit de threads disponibles. Et si chaque appel bloque le fil en attendant l'appel de service Web, vous allez bientôt frapper suffisamment d'utilisateurs simultanés que les utilisateurs devront attendre jusqu'à ce que ces appels de service Web se terminent. L'attribut Async permet au thread de revenir au pool de threads et de servir d'autres visiteurs simultanés sur votre site Web, plutôt que de le forcer à rester immobile sans rien faire en attendant le retour de l'appel du service Web.

Le résultat pour vous est le suivant: l'attribut Async est conçu pour le cas où vous ne pouvez pas rendre la page tant que la tâche asynchrone n'est pas terminée, et c'est pourquoi il ne rend pas la page immédiatement.

Vous devez lancer votre propre thread et en faire un thread démon. Je ne me souviens pas de la syntaxe exacte pour cela, mais vous pouvez facilement la trouver dans le doc en recherchant le "démon" dans le doc BCL. Cela signifie que le thread empêchera votre application de s'arrêter lorsqu'elle est en vie, ce qui est important car ASP.NET et IIS se réservent le droit de "recycler votre processus" quand ils le jugent nécessaire, et si cela se produit pendant que votre thread fonctionne, votre tâche sera arrêtée. Faire le démon de thread empêchera cela (à l'exception de quelques rares cas Edge possibles ... vous en saurez plus lorsque vous trouverez la documentation à ce sujet).

Ce thread démon est l'endroit où vous lancerez ces tâches. Et après avoir dit au thread démon de faire la tâche, vous pouvez immédiatement rendre votre page ... afin que le rendu de la page se produise immédiatement.

Encore mieux qu'un thread démon dans votre processus ASP.NET, cependant, serait d'implémenter un service Windows pour effectuer la tâche. Demandez à votre application ASP.NET de communiquer la tâche à effectuer au service. Pas besoin d'un thread démon et pas besoin de s'inquiéter du recyclage de votre processus ASP.NET. Comment dites-vous au Service de faire la tâche? Peut-être via WCF, ou peut-être en insérant un enregistrement dans une table de base de données que le service interroge. Ou plusieurs autres façons.

EDIT: Voici une autre idée, que j'ai utilisée auparavant dans le même but. Écrivez les informations sur votre tâche dans une file d'attente MSMQ. Demandez à un autre processus (peut-être même sur une autre machine) de tirer de cette file d'attente et de faire la tâche fastidieuse. Le travail d'insertion dans une file d'attente est optimisé pour revenir le plus rapidement possible, afin que votre thread ne se bloque pas pendant que les données que vous mettez dans la file d'attente sont envoyées via le fil ou quelque chose comme ça. C'est l'un des moyens les plus rapides de noter qu'une tâche doit être effectuée sans attendre qu'elle s'exécute.

26
Charlie Flowers

Vous pouvez contourner cette limitation assez facilement et sans même définir Async sur true.

public void Start()
{
    new Task(() =>
    {
        backgroundThread.RunWorkerAsync();
    }).Start();
}
3
Serge

Si vous obtenez cette erreur lors de l'appel de service Web de manière asynchrone, assurez-vous d'ajouter l'attribut Async = 'true' comme indiqué par le message d'exception?

haut de la page <Page Language = 'VB' Async = 'true' AutoEventWireup = 'false' CodeFile = 'mynewpage.aspx.vb' Inherits = 'mynewpage'%>

1
Amrik