Exception multithreading et Dispose. Pourquoi Dispose n’a pas appelé?

L’instruction ‘using’ garantit que l’object sera appelé méthode Dispose. Dans cet exemple, cela ne se produit pas. Et la méthode de finalisation n’a pas appelé aussi.

Pourquoi tout ça? Et comment puis-je changer de code pour garantir la suppression de mes objects lorsque des exceptions sur d’autres threads peuvent se produire?

class Program { static void Main(ssortingng[] args) { Thread th1 = new Thread(ThreadOne); Thread th2 = new Thread(ThreadTwo); th1.Start(); th2.Start(); th1.Join(); th2.Join(); } static void ThreadOne() { using (LockedFolder lf = new LockedFolder(@"C:\SomeFodler")) { // some pay load Thread.Sleep(5000); } } static void ThreadTwo() { // some pay load Thread.Sleep(1000); throw new Exception("Unexpected exception"); } } public class LockedFolder : IDisposable { private const ssortingng FILENAME_LOCK = ".lock-file"; private bool bLocked = false; public ssortingng FullPath { private set; get; } public LockedFolder(ssortingng FullPath) { this.FullPath = FullPath; Lock(); } private void Lock() { // lock our folder Console.WriteLine("Lock " + FullPath); //CreateLockFile(Path + FILENAME_LOCK); bLocked = true; } private void UnLock() { if (!bLocked) { Console.WriteLine("Already UnLocked " + FullPath); return; // already unlocked } Console.WriteLine("UnLock " + FullPath); // unlock our folder //DeleteLockFile(Path + FILENAME_LOCK); bLocked = false; } #region IDisposable Members private bool disposed = false; public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } public void Dispose(bool disposing) { if (!disposed) { if (disposing) { // Free managed resources } // Free unmanaged resource UnLock(); } disposed = true; } ~LockedFolder() { Dispose(false); } #endregion } 

Sortie:

\ Visual Studio 2010 \ Projects \ ExceptionExample \ ExceptionExample \ bin \ Debug> ExceptionExample.exe

Verrouiller C: \ SomeFodler

Exception non gérée: System.Exception: exception inattendue dans ExceptionExample.Program.ThreadTwo () dans \ visual studio 2010 \ Projects \ ExceptionExample \ ExceptionExample \ Program.cs: ligne 36 dans System.Threading.ThreadHelper.ThreadStart_Context (état de l’object) dans System. Threading.ExecutionContext.Run (ExecutionContext executionContext, rappel ContextCallback, état de l’object, Boolean ignoreSyncCtx) dans System.Threading.ExecutionContext.Run (ExecutionContext executionContext, rappel ContextCallback, état de l’object) dans System.Threading.ThreadHelper.

Outupt sans exception:

\ Visual Studio 2010 \ Projects \ ExceptionExample \ ExceptionExample \ bin \ Debug> ExceptionExample.exe Verrouiller C: \ SomeFodler UnLock C: \ SomeFodler

L’exception non gérée force le CLR à mettre fin au processus. Le comportement d’arrêt est légèrement différent pour .NET 4.0, le finaliseur s’exécutera après avoir signalé l’exception. Mais pas dans les versions précédentes.

Vous pouvez contourner ce problème par défaut en écrivant un gestionnaire d’événements pour AppDomain.CurrentDomain.UnhandledException. Enregistrez ou signalez l’exception et appelez Environment.Exit (). Cela permet au thread de finalisation de s’exécuter et d’appeler votre méthode Unlock ().

Ne vous fiez pas à cela, il existe de vilaines exceptions comme StackOverflow ou FEEE qui terminent le processus. Quelqu’un fait-il trébucher sur le cordon d’alimentation ou tire votre processus dans la tête avec Taskmgr.exe

Rien n’est garanti . détwigr ou mettre fin à un processus ne sera pas respecté, par exemple. Tout ce qui est garanti, c’est qu’en exécution normale (ce qui inclut la plupart des exceptions saines), il appellera Dispose() .

Dans votre cas, vous avez une exception de thread non gérée; c’est un tueur de processus. Tous les paris sont ouverts, car votre processus est maintenant maladif et en train d’être arrêté (et hors de la misère).

Si vous voulez que le code se comporte, vous devez vous assurer que vous n’avez pas d’exception de destruction de processus; les exceptions non gérées sur les threads se trouvant en haut de la liste. Un try / catch autour de n’importe quel code de niveau de thread est fortement recommandé.

La raison en est que lorsque votre processus se termine en raison d’une exception non gérée, les finaliseurs ne sont pas exécutés. Voir ici pour plus de détails. Vous pouvez forcer vos finaliseurs à exécuter l’astuce pour arrêter votre processus normalement dans le gestionnaire d’exceptions non géré à partir d’un autre thread. La stratégie du .NET Framework consiste à ne pratiquement rien faire lorsqu’une exception non gérée se produit car il n’est pas clair dans quel état le processus est laissé. Il peut être déconseillé de traiter les finaliseurs car l’état de l’application peut être corrompu et lors de la finalisation, il se produit également des exceptions qui tueraient également le thread du finaliseur. L’effet net est que seuls quelques finaliseurs ont été exécutés et le rest n’a pas été traité. Ces exceptions de suivi rendent plus difficile la recherche de la cause fondamentale de l’échec de l’application.

Bien à vous, Alois Kraus

Si vous exécutez vos threads sur un BackgroundWorker toutes les exceptions levées seront capturées dans le thread de travail et seront renvoyées comme faisant partie de l’object renvoyé par le thread. Ainsi, vous n’aurez pas à vous soucier de la fuite de vos exceptions.

Cela crée un autre problème, à savoir que vous ne pouvez pas appeler Join sur un BackgroundWorker . Cependant, vous pouvez append un Semaphore à la classe worker avec le compteur défini sur 0 (bloqué):

  private Semaphore workerFinsished = new Semaphore(0, 1); 

append une attente après avoir exécuté votre travailleur,

  public void Join() { workerFinished.WaitOne(); } 

et ajoutez une libération dans votre code de travail où vous voulez signaler que vous avez terminé.

  workerFinished.Release() 

Comme Marc l’a dit, une fois que votre application a des exceptions non gérées, tous les paris sont quasiment éteints. Pour clarifier ce que fait réellement l’utilisation, il faut un code comme celui-ci:

 using(var myDisposableObject = GetDisposableObject()) { // Do stuff with myDisposableObject } 

et le traduit en quelque chose comme ceci:

 MyDisposableObject myDisposableObject; try { myDisposableObject = GetDisposableObject(); // Do stuff with myDisposableObject } finally { if(myDisposableObject != null) { myDisposableObject.Dispose(); } } 

Alors que se passe-t-il lorsque votre application rencontre une exception non gérée? L’exception non gérée entraîne la fermeture de l’application. Cette terminaison (ou toute terminaison imprévue) peut empêcher le bloc finally de votre instruction using de s’exécuter correctement.

Vous devez toujours gérer vos exceptions, même dans le cas où vous n’entourez pas vos appels avec des threads avec des blocs try . Envisagez de vous connecter à l’événement AppDomain.UnhandledException pour nettoyer les ressources, consigner les éléments, etc. avant que votre application ne mordille à la poussière.

MODIFIER

Je viens de remarquer que Hans a posté quelque chose de similaire concernant AppDomain.UnhandledException et il a raison. Dans tous les programmes, des terminaisons imprévues peuvent donner des résultats inattendus. Toutefois, dans votre cas, comme cela a été suggéré, ne comptez pas sur l’exécution complète de votre application, en particulier avec les ressources de fichier. Pensez plutôt à écrire votre processus pour anticiper, voire même anticiper, les échecs d’exécution antérieurs. Ensuite, votre application peut traiter les exécutions incomplètes si nécessaire. Vous pouvez créer des journaux pour suivre la progression ou le marquage des étapes de votre processus et les évaluer à chaque exécution pour résoudre des états d’exécution incorrects.

En outre, vos classes (même si elles ne sont que des exemples) ne sont pas thread-safe … vous ne protégez pas vos ressources partagées.