Puis-je utiliser des threads pour effectuer des travaux de longue durée sur IIS?

85

Dans une application ASP.Net, l'utilisateur clique sur un bouton de la page Web, puis instancie un objet sur le serveur via le gestionnaire d'événements et appelle une méthode sur l'objet. La méthode est envoyée à un système externe pour faire des choses et cela peut prendre un certain temps. Donc, ce que je voudrais faire est d'exécuter cet appel de méthode dans un autre thread afin que je puisse rendre le contrôle à l'utilisateur avec "Votre demande a été soumise". Je suis raisonnablement heureux de faire cela comme feu et oublier, même si ce serait encore plus agréable si l'utilisateur pouvait continuer à interroger l'objet pour son statut.

Ce que je ne sais pas, c'est si IIS permet à mon thread de continuer à s'exécuter, même si la session utilisateur expire. Imaginez, l'utilisateur déclenche l'événement et nous instancions l'objet sur le serveur et déclenchons la méthode dans un nouveau thread. L'utilisateur est satisfait du message "Votre demande a été soumise" et ferme son navigateur. Finalement, cette session d'utilisateurs expirera sur IIS, mais le thread peut toujours être en cours d'exécution, en cours d'exécution. IIS permettra-t-il au thread de continuer à s'exécuter ou le tuera-t-il et supprimera-t-il l'objet une fois la session utilisateur expirée?

EDIT: D'après les réponses et les commentaires, je comprends que la meilleure façon de le faire est de déplacer le traitement de longue durée en dehors d'IIS. En dehors de tout le reste, cela traite du problème de recyclage du domaine d'application. En pratique, j'ai besoin de lancer la version 1 dans un temps limité et de travailler dans un cadre existant, j'aimerais donc éviter la couche de service, d'où le désir de simplement lancer le thread dans IIS. Dans la pratique, le "long fonctionnement" ici ne durera que quelques minutes et la concurrence sur le site Web sera faible, donc ça devrait aller. Mais, la prochaine version devra certainement être divisée en une couche de service distincte.

Frans
la source
1
Avez-vous plus de détails sur ce qui doit être traité et votre environnement d'hébergement? Par exemple, pouvez-vous installer un service?
senfo
Vous pouvez toujours utiliser les méthodes de programmation Async et Await (Visual Studio 2012+). Beaucoup plus propre que de gérer vous-même les threads.
SausageFingers

Réponses:

65

Vous pouvez accomplir ce que vous voulez, mais c'est généralement une mauvaise idée. Plusieurs moteurs de blog et CMS ASP.NET adoptent cette approche, car ils veulent être installables sur un système d'hébergement partagé et ne pas dépendre d'un service Windows qui doit être installé. En règle générale, ils lancent un long thread dans Global.asax lorsque l'application démarre, et ce processus de thread met des tâches en file d'attente.

En plus de réduire les ressources disponibles pour IIS / ASP.NET pour traiter les demandes, vous rencontrez également des problèmes avec le thread en cours de suppression lorsque l'AppDomain est recyclé, puis vous devez gérer la persistance de la tâche pendant qu'elle est en cours, comme ainsi que de redémarrer le travail lorsque l'AppDomain revient.

Gardez à l'esprit que dans de nombreux cas, l'AppDomain est recyclé automatiquement à un intervalle par défaut, ainsi que si vous mettez à jour le web.config, etc.

Si vous pouvez gérer la persistance et les aspects transactionnels de la suppression de votre thread à tout moment, vous pouvez contourner le recyclage AppDomain en ayant un processus externe qui fait une demande sur votre site à un certain intervalle - de sorte que si le site est recyclé, vous sont garantis de redémarrer automatiquement dans les X minutes.

Encore une fois, c'est généralement une mauvaise idée.

EDIT: Voici quelques exemples de cette technique en action:

Serveur de communauté: utilisation des services Windows par rapport au thread d'arrière-plan pour exécuter du code à des intervalles planifiés Création d'un thread d'arrière-plan au démarrage du site Web

EDIT (du futur lointain) - Ces jours-ci, j'utiliserais Hangfire .

Bryan Batchelder
la source
1
Merci, c'est perspicace. Le recyclage est un tueur incontestable et je déduis de ce que vous dites que je peux le faire de manière difficile et rapide, mais c'est périlleux.
Frans le
Oui, vérifiez ceci: professionalaspnet.com/archive/2007/09/02/ ... Et voici un fil de discussion sur la façon dont COmmunity Server gère la même chose: dev.communityserver.com/forums/t/459942.aspx
Bryan Batchelder
Dans mon cas, j'ai pu simplement dire à IIS de ne jamais recycler et de ne jamais inactiver le site. Cela m'a permis de travailler sous cette hypothèse et que le site ne serait recyclé que lors du déploiement et à chaque fois qu'une maintenance planifiée se produirait. Puisque je faisais juste un site de type administrateur, cela a fonctionné parfaitement pour moi.
Brett
1
Bizarre que cela ait autant de votes positifs. Que cette chose soit possible est l'une des choses très intéressantes à propos d'ASP.NET: que toutes les demandes soient servies dans le même AppDomain avec tous les threads d'arrière-plan que vous pouvez créer. Aucune des raisons données pour lesquelles il s'agit d'une «mauvaise idée» ne me paraît convaincante. AppDomains peut descendre, oui - mais ce n'est pas unique à l'environnement ASP.NET. Vous devez bien jouer avec votre environnement d'hébergement (google for HostingEnvironment.RegisterObject).
John le
3
.NET 4.5.2 a ajouté une nouvelle fonctionnalité QueueBackgroundWorkItempour enregistrer facilement les tâches en arrière-plan, vous voudrez peut-être mettre à jour votre réponse à nouveau.
Scott Chamberlain
39

Je ne suis pas d'accord avec la réponse acceptée.

L'utilisation d'un thread d'arrière-plan (ou d'une tâche démarrée par Task.Factory.StartNew) convient parfaitement dans ASP.NET. Comme pour tous les environnements d'hébergement, vous souhaiterez peut-être comprendre et coopérer avec les installations régissant l'arrêt.

Dans ASP.NET, vous pouvez enregistrer le travail devant s'arrêter correctement à l'arrêt à l'aide de la HostingEnvironment.RegisterObjectméthode. Voir cet article et les commentaires pour une discussion.

(Comme le souligne Gerard dans son commentaire, il y a maintenant aussi des HostingEnvironment.QueueBackgroundWorkItemappels à RegisterObjectpour enregistrer un planificateur pour l'élément d'arrière-plan sur lequel travailler. Dans l'ensemble, la nouvelle méthode est plus agréable car elle est basée sur les tâches.)

En ce qui concerne le thème général que vous entendez souvent dire qu'il s'agit d'une mauvaise idée, envisagez l'alternative de déployer un service Windows (ou un autre type d'application extra-processus):

  • Plus de déploiement trivial avec le déploiement Web
  • Non déployable uniquement sur les sites Web Azure
  • Selon la nature de la tâche d'arrière-plan, les processus devront probablement communiquer. Cela signifie que soit une forme d'IPC, soit le service devra accéder à une base de données commune.

Notez également que certains scénarios avancés peuvent même nécessiter que le thread d'arrière-plan s'exécute dans le même espace d'adressage que les demandes. Je vois le fait qu'ASP.NET peut faire cela comme un grand avantage qui est devenu possible grâce à .NET.

John
la source
C'est bien. J'ai une tâche qui nécessite environ 30 secondes. Parfait si vous avez juste besoin de retarder le pool d'applications assez longtemps pour faire le travail.
Scuba Steve
1
Depuis .Net Framework 4.5.2, vous pouvez également utiliser HostingEnvironment.QueueBackgroundWorkItem (Action <CancellationToken>)
Gerard Carbó
Dans mes observations, il semble qu'IIS arrête finalement l'API Web ASP.NET que j'exécute et ne la redémarre pas avant qu'une nouvelle demande n'arrive. Ainsi, à l'heure fixée, mon thread est censé lancer l'application peut même pas courir. Est-ce que je me trompe?
David Sopko du
7

Vous ne voudriez pas utiliser un thread du pool de threads IIS pour cette tâche car cela laisserait ce thread incapable de traiter les demandes futures. Vous pouvez examiner les pages asynchrones dans ASP.NET 2.0 , mais ce ne serait pas non plus la bonne réponse. Au lieu de cela, il semble que vous bénéficieriez de la recherche dans Microsoft Message Queuing . Essentiellement, vous ajouteriez les détails de la tâche à la file d'attente et un autre processus d'arrière-plan (éventuellement un service Windows) serait en charge d'exécuter cette tâche. Mais l'essentiel est que le processus d'arrière-plan est complètement isolé d'IIS.

senfo
la source
Ah, MSMQ - une de mes technologies préférées à long terme. Merci pour la perspicacité et le lien.
Frans le
6

Je suggérerais d'utiliser HangFire pour de telles exigences. C'est un bon moteur fire and forget fonctionne en arrière-plan, prend en charge différentes architectures, fiable car il est soutenu par un stockage de persistance.

Vipul
la source
5

Il existe un bon thread et un exemple de code ici: http://forums.asp.net/t/1534903.aspx?PageIndex=2

J'ai même joué avec l'idée d'appeler une page Keep Alive sur mon site Web à partir du fil de discussion pour aider à maintenir le pool d'applications en vie. Gardez à l'esprit que si vous utilisez cette méthode, vous avez besoin d'une très bonne gestion de la récupération, car l'application peut recycler à tout moment. Comme beaucoup l'ont mentionné, ce n'est pas la bonne approche si vous avez accès à d'autres options de service, mais pour l'hébergement partagé, cela peut être l'une de vos seules options.

Pour aider à maintenir le pool d'applications en vie, vous pouvez envoyer une demande à votre propre site pendant le traitement du thread. Cela peut aider à maintenir le pool d'applications actif si votre processus s'exécute pendant une longue période.

string tempStr = GetUrlPageSource("http://www.mysite.com/keepalive.aspx");


    public static string GetUrlPageSource(string url)
    {
        string returnString = "";

        try
        {
            Uri uri = new Uri(url);
            if (uri.Scheme == Uri.UriSchemeHttp)
            {
                HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri);
                CookieContainer cookieJar = new CookieContainer();

                req.CookieContainer = cookieJar;

                //set the request timeout to 60 seconds
                req.Timeout = 60000;
                req.UserAgent = "MyAgent";

                //we do not want to request a persistent connection
                req.KeepAlive = false;

                HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
                Stream stream = resp.GetResponseStream();
                StreamReader sr = new StreamReader(stream);

                returnString = sr.ReadToEnd();

                sr.Close();
                stream.Close();
                resp.Close();
            }
        }
        catch
        {
            returnString = "";
        }

        return returnString;
    }
Daniel
la source
5

Nous avons commencé dans cette voie, et cela fonctionnait bien lorsque notre application était sur un serveur. Lorsque nous voulions évoluer vers plusieurs machines (ou utiliser plusieurs w3wp dans un site Web), nous devions réévaluer et examiner comment gérer une file d'attente de travail, la gestion des erreurs, les tentatives et le problème délicat du verrouillage correct pour garantir un seul le serveur récupère l'élément suivant.

... nous avons réalisé que nous ne sommes pas dans le domaine de l'écriture de moteurs de traitement en arrière-plan, nous avons donc cherché des solutions existantes et nous avons atterri en utilisant le génial projet OSS hangfire

Sergey Odinokov a créé un véritable joyau avec lequel il est très facile de démarrer et qui vous permet d'échanger le backend sur la façon dont le travail est conservé et mis en file d'attente. Hangfire utilise des threads d'arrière-plan, mais persiste les travaux, gère les tentatives et vous donne une visibilité sur la file d'attente de travail. Les jobs hangfire sont donc robustes et survivent à tous les aléas du recyclage des domaines d'applications, etc.

Sa configuration de base utilise le serveur SQL comme stockage, mais vous pouvez remplacer Redis ou MSMQ lorsqu'il est temps de passer à l'échelle. Il dispose également d'une excellente interface utilisateur pour visualiser tous les travaux et leur état, en plus de vous permettre de remettre les travaux en file d'attente.

Mon point est que s'il est tout à fait possible de faire ce que vous voulez dans un thread d'arrière-plan, il y a beaucoup de travail pour le rendre évolutif et robuste. C'est bien pour les charges de travail simples, mais lorsque les choses deviennent plus complexes, je préfère de loin utiliser une bibliothèque spécialement conçue à cet effet plutôt que de passer par cet effort.

Pour plus de perspectives sur les options disponibles, consultez le blog de Scott Hanselman qui couvre quelques options de gestion des tâches d'arrière-plan dans asp.net. (Il a donné à Hangfire une critique élogieuse)

Aussi, comme mentionné par John, il vaut la peine de lire le blog de Phil Haack sur les raisons pour lesquelles l'approche est problématique et comment arrêter gracieusement le travail sur le fil lorsque le domaine d'application est déchargé.

Avner
la source
2

Pouvez-vous créer un service Windows pour effectuer cette tâche? Ensuite, utilisez .NET Remoting à partir du serveur Web pour appeler le service Windows pour effectuer l'action? Si tel est le cas, c'est ce que je ferais.

Cela éliminerait le besoin de relais sur IIS et limiterait une partie de sa puissance de traitement.

Sinon, je forcerais l'utilisateur à rester assis pendant que le processus est terminé. De cette façon, vous vous assurez qu'il est terminé et non tué par IIS.

David Basarab
la source
2

Il semble y avoir une manière prise en charge d'héberger des travaux de longue durée dans IIS. Les services de flux de travail semblent conçus pour cela, en particulier en conjonction avec Windows Server AppFabric . La conception permet le recyclage du pool d'applications en prenant en charge la persistance automatique et la reprise du travail de longue durée.

Olly
la source
2

Vous pouvez exécuter des tâches en arrière-plan et elles se termineront même après la fin de la demande. Ne laissez pas une exception non interceptée être lancée . Normalement, vous voulez toujours lever vos exceptions. Si une exception est levée dans un nouveau thread, le processus de travail IIS - w3wp.exe sera bloqué , car vous n'êtes plus dans le contexte de la demande. Cela va également tuer toutes les autres tâches d'arrière-plan que vous exécutez en plus des sessions sauvegardées en mémoire en cours si vous les utilisez. Ce serait difficile à diagnostiquer, c'est pourquoi la pratique est déconseillée.

Timothy Gonzalez
la source
1

Créez simplement un processus de substitution pour exécuter les tâches asynchrones; il n'est pas nécessaire que ce soit un service Windows (bien que ce soit l'approche la plus optimale dans la plupart des cas. MSMQ est bien sur tuer.

Matt Davison
la source