Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Atteint de JavaScriptite Aiguë [Cyril Durand]

Expert ASP.net Ajax et WCF, Cyril Durand parle dans son blog de point techniques sur ASP.net, ASP.net Ajax, JavaScript, WCF et .net en général. Cyril est également consultant indépendant, n'hésitez pas à le contacter pour de l'assistance sur vos projets

Actualités

  • Blog de Cyril DURAND, passionné de JavaScript, Ajax, ASP.net et tout ce qui touche au developpement Web Client-Side.

    Consultant freelance, n'hésitez pas à me contacter pour vos projets .net : architecture, accompagnement, formation, ...

    View Cyril Durand's profile on LinkedIn
    hit counters


    Expertise Commerce server et BizTalk

ASP.net : Response.Redirect – Attention au piège. “Thread was being aborted.” / “Le thread a été abandonné.” - l’exception ThreadAbortException

La méthode Response.Redirect permet de rediriger un utilisateur vers une URL, pour cela ASP.net utilise le code d’erreur HTTP 302.

Je me suis récemment retrouvé face à un scénario où cette méthode m’a posé problème.
Au niveau de mon application, je lance une opération complexe utilisant des transactions et redirigeant l’utilisateur lorsque le traitement se déroule avec succès. En cas d’erreur, je fais un rollback sur mes transactions. Ci-dessous, le pseudo code illustrant le problème :

try { // Complex operation call which use transaction Response.Redirect("otherPage.aspx"); } catch (Exception) { // transaction.rollback() throw; }

Mon problème est que dans tous les cas le rollback est exécuté, c’est à dire que le code passe dans le catch, cependant rien n’est affiché au client.

Après quelques minutes de debug, je me suis rendu compte que le message d’erreur était “Thread was being aborted.” ou bien en français “Le thread a été abandonné.”. Ce problème vient du fonctionnement interne du Response.Redirect, en effet celui-ci lance une exception afin de stoper la requête active.

Voyons comment Response.Redirect fonctionne.

Response.Redirect possède 2 signatures, l’une qui nécessite seulement une url, et l’autre qui possède en plus un paramètre endResponse. Voici le pseudo code de la méthode Redirect.

public void Redirect(string url) { this.Redirect(url, true); } public void Redirect(string url, bool endResponse) { // writing some HTML and 302 status code if(endResponse){ Response.End(); } }

Le paramètre endResponse permet de mettre fin à l’exécution de la requête, pour cela ASP.net appelle la méthode Response.End qui met fin au thread courant via la méthode Thread.CurrentThread.Abort. C’est dans cette dernière qu’une exception de type ThreadAbortException est levé. Cette exception est cependant déclenché après l’envoie de la réponse au client, c’est pour cela que l’on ne voit aucune erreur coté client.

Pour résoudre ce soucis, le plus simple est d’ignorer les exceptions de type ThreadAbortException.

try { // Complex operation call which use transaction Response.Redirect("otherPage.aspx"); } catch (ThreadAbortException) {/* do nothing, it might be a response.Redirect exception */} catch (Exception) { // transaction.rollback }

Bien sur, ce bout de code n’est pas optimal, cela risque de cacher les vrais exceptions de type ThreadAbortException, celles-ci sont plutôt rares, on peut généralement les ignorer.

Cependant, si vous voulez faire les choses un peu plus proprement, il est possible d’analyser le callstack afin de savoir de quelle méthode provient l’exception.

private static MethodInfo _redirectMethod = typeof(HttpResponse) .GetMethod("Redirect", new Type[] { typeof(String), typeof(Boolean) }); // ... try { Response.Redirect("otherPage.aspx"); } catch (Exception ex) { if (ex is ThreadAbortException) { Boolean ignore = false; // use with caution if internal implementation change, // this solution can be broken // a little expensive - see JBEvain's comment StackTrace stackTrace = new StackTrace(ex); for (int i = stackTrace.FrameCount - 1; i > 0; i--) { StackFrame frame = stackTrace.GetFrame(i); if (frame.GetMethod() == _redirectMethod) { ignore = true; break; } } if (ignore) { Debug.WriteLine("Redirect exception"); return; } } Debug.WriteLine("Not a redirect exception"); }

Comme nous l’a fait remarquer JBEvain dans les commentaires, cette solution n’est pas idéale. L’analyse du StackTrace est un peu couteuse et surtout si l’implémentation interne du Redirect change, il se peut que le code ne fonctionne plus. De plus, cela rend le code moins lisible, cette dernière solution est donc à utiliser seulement si vous en avez vraiment besoin.

Des questions ?

Posted: mardi 19 mai 2009 02:46 par cyril
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Commentaires

maitredede a dit :

Bonjour,

Je me suis retrouvé dans un cas similaire au tiens, sauf que au lieux de rollback, je perdais le cookie d'authentification, et donc mes utilisateurs n'étaient plus authentifiés.

J'ai lu dans un article (dont je n'ai plus l'adresse) qu'il vallait mieux faire un Response.Redirect(url, false), pour éviter un ThreadAbortException...

Par contre, je n'ai pas creusé ce que ça peut donner de faire un Response.Redirect(url, false) suivi d'un Response.End()...

# mai 19, 2009 08:47

Jb Evain a dit :

Si l'utilisation de StackTrace est pratique dans des situations de debug, je ne voudrais pas avoir du code de production qui en dépend.

Si dans un service pack de .net ils changent l'implémentation de HttpResponse.Redirect, pour par exemple, appeler une méthode RedirectImpl, l'api publique ne change pas, seul un détail d'implémentation change. Ça c'est déjà vu.

La méthode Redirect, étant réduite, sera éligible par le JIT pour être inlinée, et disparaîtra de la StackTrace. D'autant plus que nous n'avons que très peu de contrôle sur le JIT, à part pouvoir désactiver l'inlining de notre code avec un attribute. Ça et le fait que récupéré un objet StackTrace est un procédé coûteux en temps.

Bien sur je fais un cas de cet exemple, mais on voit beaucoup d'utilisation de StackTrace, qui sont potentiellement dangereuses.

# mai 19, 2009 09:14

cyril a dit :

@maitredede : Response.Redirect(url, false) est très rarement utile. Cela continue la requête (alors que la réponse est déjà prete) et utilise donc des ressources CPU inutile.

Je prefere avoir une exception plutot que de surcharger mon CPU.

# mai 19, 2009 09:40

KooKiz a dit :

Il faut aussi considérer la lisibilité du code. Ajouter presque 10 lignes juste pour éviter de consommer un peu de CPU, c'est de l'overkill...

# mai 19, 2009 09:57

cyril a dit :

@JB : très bonne remarque ! Du coup, il n'y a pas de solution propre pour ce que je veux.

Je vais éditer mon post pour prendre en compte ta remarque.

# mai 19, 2009 09:58

cyril a dit :

@KooKiz : vivi, je suis d'accord. Rajouter les 10 lignes de codes "incompréhensible" n'est pas la solution à utiliser dans tous les cas. Il faut l'utiliser seulement si l'on en a vraiment besoin. Je ne vois pas quand on peut en avoir besoin :)

# mai 19, 2009 10:03

FREMYCOMPANY a dit :

Plutôt  ue d'analyser la Stack, ce qui est assez couteux, on peut aussi s'imposer d'écrire :

Try : Response.Redirect("otherPage.aspx") : Catch Ex as ThreadAbortException : End Try

Exit Sub/Function

Si besoin est, on peut aussi lancer une autre erreur, que l'on pourrait reconnaitre avec son type ou par la clause When.

# mai 20, 2009 07:11

smo a dit :

Comme indiqué plus haut, il vaut largement mieux faire un Response.Redirect(url, false), en laissant le programmer s'exécuter normalement. Tuer les thread violemment est généralement une très mauvaise idée, surtout dans un serveur. cf ici: http://www.interact-sw.co.uk/iangblog/2004/11/12/cancellation

# mai 20, 2009 07:42

cyril a dit :

@FremyCompany : c'est la solution que je propose et que j'utilise, l'autre solution est là seulement pour le "fun", au cas où, si besoin.  

# mai 20, 2009 11:52

cyril a dit :

@smo : je n'ai pas lu tout l'article cité, mais je ne vois pas quelle est le gros problème avec le Thread.Abort(). OK cela lève une exception, OK c'est un petit peu lourd, OK c'est pas propre.

Mais entre cette solution et effectué tout le binding et le rendering (inutiles) d'une page je choisis de faire un Thread.Abort. Cela permet en plus de rendre rapidement un thread au pool de thread, de consommer moins de CPU, et d'éviter les IO vers la base inutiles.

Bien sur, mettre en place une solution alternative qui permettrais de stopper le thread proprement (via un flag) serait un plus. Mais cela complexifierais le code et surtout la partie rendering (géré par ASP.net) sera effectué dans tous les cas.

donc NON au response.redirect(url, false) sauf si l'on a une bonne raison (autre que ne pas faire un Thread.Abort)

# mai 20, 2009 11:57
Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- Nouveau système d'aide pour Visual Studio 2010 : pour ceux qui n'apprécient pas trop l'absence d'index... par CoqBlog le il y a 30 minutes

- L'interface naturelle de Windows Phone 7 Series par Perspective le il y a 1 heure et 46 minutes

- Comment mapper une vue SQL sur une collection de complex type? par Matthieu MEZIL le il y a 23 heures et 30 minutes

- SQL Server : Query Notification ou comment être notifié de modifications de données côté application (SqlDependency) par SQL Server vu par Christian Robert le 03-19-2010, 15:06

- [WF4] Un Binding Activity/ActivityDesigner qui passe mal? par Blog de Jérémy Jeanson le 03-19-2010, 13:42

- MyTIC – SharePoint 2010 : déjà un mythe Microsoft ? par Le Blog (Vert) d'Arnaud JUND le 03-19-2010, 08:54

- TechDays 2010 Genève : Retrouvez-moi pour une session sur la Haute disponibilité et le ScaleOut avec SQL Server par SQL Server vu par Christian Robert le 03-18-2010, 15:45

- [MIX10] Keynote deuxième journée – Internet Explorer 9, Html5, Visual Studio 2010, OData par Atteint de JavaScriptite Aiguë [Cyril Durand] le 03-17-2010, 19:40

- Certifications beta .NET 4 par Kévin Gosse le 03-17-2010, 19:33

- [Mix 2010] – Microsoft Translator Technology Preview V2 par RedoBlog - The .NET Gentleman !!! le 03-17-2010, 18:53