Question

Le code que je veux écrire ressemble à ceci :

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 demander au thread B de vérifier périodiquement, de manière thread-safe, si un indicateur a été défini par le thread A, mais cela rend le code plus compliqué.Existe-t-il un meilleur mécanisme que je peux utiliser ?

Voici un exemple plus détaillé de vérification périodique :

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.
    }
}
Était-ce utile?

La solution

Ce n'est pas une bonne idée

Cet article parle de la bibliothèque de délais d'attente de Ruby. qui lève des exceptions entre les threads.

Cela explique à quel point faire une telle chose est fondamentalement brisé.Ce n'est pas seulement cassé dans Ruby, il est cassé partout où des exceptions sont générées entre les threads.

En un mot, voici ce qui peut arriver (et se produit) :

Sujet A :

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

Sujet B :

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 « vérification périodique » convient, car vous ne lancez pas réellement d'exceptions entre les threads.
Vous définissez simplement un drapeau qui dit "lancez une exception la prochaine fois que vous regardez ce drapeau", ce qui est bien car il ne souffre pas du problème "peut être lancé au milieu de votre capture ou finalement bloquer".
Cependant, si vous comptez faire cela, vous pouvez tout aussi bien définir un indicateur "exitnow", l'utiliser et vous épargner les tracas liés à la création de l'objet d'exception.Un booléen volatile fonctionnera très bien pour cela.

Autres conseils

Il y a suffisamment de problèmes avec les exceptions qui peuvent être lancées sur les threads par d'autres mécanismes, comme l'abandon des threads et autres, pour que vous devriez trouver une autre façon de le faire.

Une exception est un mécanisme utilisé pour signaler qu'un processus a vécu quelque chose d'exceptionnel qu'il ne peut pas gérer.Vous devriez essayer d'éviter d'écrire le code de manière à ce qu'une exception soit utilisée pour signaler que autre chose a vécu 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 l'utilisation d'exceptions.

Utilisez des objets événementiels ou similaires pour indiquer à un thread d'abandonner son traitement, c'est la meilleure façon.

En recherchant un autre problème, je suis tombé sur cet article qui m'a rappelé votre question :

Plomberie des profondeurs de ThreadAbortException à l'aide de Rotor

Il montre les girations que traverse .NET pour implémenter Thread.Abort() - probablement toute autre exception cross-thread devrait être similaire.(Ouais !)

Ce que dit Orion Edwards n’est pas entièrement vrai :n'est pas le « seul » moyen.

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

Utilisation de CER (Régions d'exécution contrainte) en C# vous permet de libérer vos ressources en tant qu'opération atomique, protégeant votre code des 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 publié 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 définir de manière fiable des poignées à l'aide de l'option PrepareConstrainedRegions méthode.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 SafeHandle l'objet est atomique.Tout échec entre ces opérations (comme un abandon de thread ou une exception de mémoire insuffisante) entraînera une fuite du handle natif.Vous pouvez utiliser le PrepareConstrainedRegions méthode pour s’assurer que la poignée ne fuit pas.

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;
}

Je suis intéressé de savoir pourquoi vous voudriez faire cela.Il n’y a pas de manière simple de procéder, car ce n’est pas une bonne pratique.Vous devriez probablement revenir à votre conception et trouver un moyen plus propre d'atteindre l'objectif final.

Je ne pense pas que ce soit une bonne idée..Résolvez à nouveau ce problème : essayez d'utiliser un autre mécanisme, tel que des données partagées, pour signaler entre les threads.

Comme les autres, je ne suis pas sûr que ce soit une si bonne idée, mais si vous voulez vraiment le faire, vous pouvez créer une sous-classe de SynchronizationContext qui permet de publier et d'envoyer des délégués au thread cible (s'il s'agit d'un thread WinForms, le travail est fait pour vous car une telle sous-classe existe déjà).Le thread cible devra cependant implémenter une sorte d’équivalent de pompe de messages pour recevoir les délégués.

@Orion Edwards

Je comprends votre point de vue concernant une exception levée dans le bloc final.

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

Sujet A :

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

Sujet 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();
}

Sujet 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 juste idiot ?

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top