2008-09-04 7 views
11

Le code que je veux écrire est comme ceci:Y at-il une bonne méthode en C# pour lancer une exception sur un fil donné

void MethodOnThreadA() 
{ 
    for (;;) 
    { 
     // Do stuff 
     if (ErrorConditionMet) 
      ThrowOnThread(threadB, new MyException(...)); 
    } 
} 

void MethodOnThreadB() 
{ 
    try 
    { 
     for (;;) 
     { 
      // Do stuff 
     } 
    } 
    catch (MyException ex) 
    { 
     // Do the right thing for this exception. 
    } 
} 

Je sais que je peux avoir le thread B vérifier périodiquement, en fil de manière sûre , pour voir si un drapeau a été défini par thread A, mais cela complique le code. Y a-t-il un meilleur mécanisme que je peux utiliser?

Voici une plus étoffée par exemple de vérifier périodiquement:

Dictionary<Thread, Exception> exceptionDictionary = new Dictionary<Thread, Exception>(); 

void ThrowOnThread(Thread thread, Exception ex) 
{ 
    // the exception passed in is going to be handed off to another thread, 
    // so it needs to be thread safe. 
    lock (exceptionDictionary) 
    { 
     exceptionDictionary[thread] = ex; 
    } 
} 

void ExceptionCheck() 
{ 
    lock (exceptionDictionary) 
    { 
     Exception ex; 
     if (exceptionDictionary.TryGetValue(Thread.CurrentThread, out ex)) 
      throw ex; 
    } 
} 

void MethodOnThreadA() 
{ 
    for (;;) 
    { 
     // Do stuff 
     if (ErrorConditionMet) 
      ThrowOnThread(threadB, new MyException(...)); 
    } 
} 

void MethodOnThreadB() 
{ 
    try 
    { 
     for (;;) 
     { 
      // Do stuff 
      ExceptionCheck(); 
     } 
    } 
    catch (MyException ex) 
    { 
     // Do the right thing for this exception. 
    } 
} 
+0

Oh wow, si cela était possible, vous devriez vous attendre, il est possible de voir à même un simple ajout d'une exception levée. – Dykam

Répondre

10

Ce n'est pas une bonne idée

This article talks about ruby's timeout library. qui jette des exceptions dans les threads.

Il explique comment faire une telle chose est fondamentalement brisé. Ce n'est pas seulement cassé en rubis, il est cassé partout où il y a des exceptions sur les threads.

En un mot, ce qui peut (et ne) s'est le suivant:

ThreadA:

At some random time, throw an exception on thread B: 

ThreadB:

try { 
    //do stuff 
} finally { 
    CloseResourceOne(); 
    // ThreadA's exception gets thrown NOW, in the middle 
    // of our finally block and resource two NEVER gets closed. 
    // Obviously this is BAD, and the only way to stop is to NOT throw 
    // exceptions across threads 
    CloseResourceTwo(); 
} 

Votre exemple de 'contrôle périodique' est bien, comme vous ne lancez pas d'exceptions sur les threads.
Vous êtes en train de placer un drapeau qui dit "jeter une exception la prochaine fois que vous regardez ce drapeau", ce qui est bien car il ne souffre pas du "peut être jeté au milieu de votre capture ou bloquer finalement" problème.
Cependant, si vous allez faire cela, vous pouvez tout simplement définir un drapeau "exitnow", et l'utiliser et vous éviter les tracas de créer l'objet d'exception. Un bool volatile fonctionnera très bien pour cela.

10

Il y a assez de problèmes avec des exceptions qui peuvent être levées sur les filets par d'autres mécanismes, comme des fils abandon et les goûts, que vous devriez trouver un autre façon de le faire.

Une exception est un mécanisme utilisé pour signaler qu'un processus a connu quelque chose d'exceptionnel auquel il ne peut pas faire face. Vous devriez essayer d'éviter d'écrire le code afin qu'une exception soit utilisée pour signaler que quelque chose d'autre a connu quelque chose d'exceptionnel.

Cet autre thread ne saura probablement pas comment gérer l'exception dans tous les cas où il pourrait être lancé par votre code. En bref, vous devriez trouver un autre mécanisme pour abandonner vos threads que d'utiliser des exceptions. Utilisez des objets d'événement ou similaires pour dire à un thread d'abandonner son traitement, c'est la meilleure façon de le faire.

0

Je suis curieux de savoir pourquoi vous voudriez faire cela. Il n'y a pas de moyen facile de le faire, car ce n'est pas une bonne pratique. Vous devriez probablement revenir à votre conception et trouver une façon plus propre d'atteindre l'objectif final.

0

Je ne pense pas que ce soit une bonne idée .. Faites une autre fissure à ce problème - Essayez d'utiliser un autre mécanisme comme les données partagées pour signaler entre les threads.

0

Comme les autres, je ne suis pas sûr que ce soit une bonne idée, mais si vous voulez vraiment le faire, vous pouvez créer une sous-classe de SynchronizationContext qui permet de poster et d'envoyer des délégués au thread cible (si c'est un WinForms thread le travail est fait pour vous en tant que telle une sous-classe existe déjà). Le thread cible devra cependant mettre en place une sorte de pompe à message équivalente pour recevoir les délégués.

0

@Orion Edwards

Je comprends votre point au sujet d'une exception levée dans le bloc finally.

Cependant, je pense qu'il existe un moyen - en utilisant encore un autre thread - d'utiliser cette idée d'exception en tant qu'interruption.

Discussion A:

At some random time, throw an exception on thread C: 

Discussion B:

try { 
    Signal thread C that exceptions may be thrown 
    //do stuff, without needing to check exit conditions 
    Signal thread C that exceptions may no longer be thrown 
} 
catch { 
    // exception/interrupt occurred handle... 
} 
finally { 
    // ...and clean up 
    CloseResourceOne(); 
    CloseResourceTwo(); 
} 

Discussion C:

while(thread-B-wants-exceptions) { 
     try { 
      Thread.Sleep(1) 
     } 
     catch { 
      // exception was thrown... 
      if Thread B still wants to handle exceptions 
       throw-in-B 
     } 
    } 

Ou est-ce tout simplement ridicule?

2

Lors de mes recherches une autre question, je suis tombé sur cet article qui m'a rappelé de votre question:

Plumbing the Depths of the ThreadAbortException using Rotor

Il montre les soubresauts que .NET passe par la mise en œuvre Thread.Abort() - vraisemblablement tout Une autre exception devrait être similaire. (Yeech!)

1

Ce que dit Orion Edwards n'est pas tout à fait vrai: ce n'est pas le "seul" moyen.

// Obviously this is BAD, and the only way to stop is to NOT throw 
// exceptions across threads 

CER Utiliser (Régions d'exécution avec contraintes) en C# vous permet de libérer vos ressources comme une opération atomique, la protection de votre code d'exceptions inter-thread. Cette technique est utilisée par plusieurs classes du .NET Framework qui fonctionnent avec l'API native de Windows, où un handle non libéré peut provoquer une fuite de mémoire.

Voir http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.runtimehelpers.prepareconstrainedregions.aspx

L'exemple suivant montre comment les poignées mis de manière fiable en utilisant la méthode PrepareConstrainedRegions. Pour définir de manière fiable un handle sur un handle préexistant spécifié, vous devez vous assurer que l'allocation du handle natif et l'enregistrement ultérieur de ce handle dans un objet SafeHandle sont atomiques. Tout échec entre ces opérations (comme une interruption de thread ou une exception de mémoire insuffisante) entraînera la fuite du handle natif. Vous pouvez utiliser la méthode PrepareConstrainedRegions pour vous assurer que la poignée n'est pas divulguée.

Aussi simple que:

public MySafeHandle AllocateHandle() 
{ 
    // Allocate SafeHandle first to avoid failure later. 
    MySafeHandle sh = new MySafeHandle(); 

    RuntimeHelpers.PrepareConstrainedRegions(); 
    try { } 
    finally // this finally block is atomic an uninterruptible by inter-thread exceptions 
    { 
     MyStruct myStruct = new MyStruct(); 
     NativeAllocateHandle(ref myStruct); 
     sh.SetHandle(myStruct.m_outputHandle); 
    } 

    return sh; 
} 
Questions connexes