Gibt es in C# eine gute Methode zum Auslösen einer Ausnahme in einem bestimmten Thread?

StackOverflow https://stackoverflow.com/questions/44656

  •  09-06-2019
  •  | 
  •  

Frage

Der Code, den ich schreiben möchte, sieht so aus:

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

Ich weiß, dass ich Thread B regelmäßig auf Thread-sichere Weise überprüfen lassen kann, ob ein Flag von Thread A gesetzt wurde, aber das macht den Code komplizierter.Gibt es einen besseren Mechanismus, den ich verwenden kann?

Hier ist ein ausführlicheres Beispiel für die regelmäßige Überprüfung:

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.
    }
}
War es hilfreich?

Lösung

Das ist KEINE gute Idee

In diesem Artikel geht es um die Timeout-Bibliothek von Ruby. was Ausnahmen über Threads hinweg auslöst.

Es erklärt, warum es grundsätzlich fehlerhaft ist, so etwas zu tun.Es ist nicht nur in Ruby kaputt, es ist überall kaputt, wo Ausnahmen über Threads hinweg ausgelöst werden.

Kurz gesagt, was passieren kann (und passiert):

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

Ihr Beispiel für die „regelmäßige Überprüfung“ ist in Ordnung, da Sie tatsächlich keine Ausnahmen über Threads hinweg auslösen.
Sie setzen lediglich ein Flag mit der Aufschrift „Wirf eine Ausnahme aus, wenn Sie das nächste Mal auf dieses Flag schauen“. Dies ist in Ordnung, da es nicht unter dem Problem „Kann mitten in Ihrem Fang geworfen oder schließlich blockiert werden“ leidet.
Wenn Sie dies jedoch tun möchten, können Sie genauso gut einfach ein „exitnow“-Flag setzen und dieses verwenden und sich so die Mühe ersparen, das Ausnahmeobjekt zu erstellen.Ein flüchtiger Bool wird dafür gut funktionieren.

Andere Tipps

Es gibt genügend Probleme mit Ausnahmen, die durch andere Mechanismen wie das Abbrechen von Threads usw. auf Threads ausgelöst werden können, sodass Sie einen anderen Weg finden sollten, dies zu tun.

Eine Ausnahme ist ein Mechanismus, der signalisiert, dass ein Prozess etwas Außergewöhnliches erlebt hat, mit dem er nicht umgehen kann.Sie sollten versuchen, das Schreiben des Codes so zu vermeiden, dass eine Ausnahme verwendet wird, um dies zu signalisieren etwas anderes hat etwas Außergewöhnliches erlebt.

Dieser andere Thread wird höchstwahrscheinlich nicht wissen, wie er mit der Ausnahme umgehen soll in allen Fällen, in denen es durch Ihren Code ausgelöst werden könnte.

Kurz gesagt, Sie sollten einen anderen Mechanismus zum Abbrechen Ihrer Threads finden als die Verwendung von Ausnahmen.

Verwenden Sie Ereignisobjekte oder ähnliches, um einem Thread mitzuteilen, dass er seine Verarbeitung abbrechen soll. Das ist der beste Weg.

Bei der Recherche zu einem anderen Problem bin ich auf diesen Artikel gestoßen, der mich an Ihre Frage erinnert:

Ausloten der Tiefen der ThreadAbortException mithilfe von Rotor

Es zeigt die Schwankungen, die .NET durchläuft, um Thread.Abort() zu implementieren – vermutlich müsste jede andere Thread-übergreifende Ausnahme ähnlich sein.(Yeech!)

Was Orion Edwards sagt, ist nicht ganz wahr:ist nicht der „einzige“ Weg.

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

Mit CER (Eingeschränkte Ausführungsregionen) in C# ermöglicht es Ihnen, Ihre Ressourcen als atomaren Vorgang freizugeben und so Ihren Code vor Inter-Thread-Ausnahmen zu schützen.Diese Technik wird von mehreren Klassen des .NET Framework verwendet, die mit der nativen API von Windows arbeiten, wobei ein nicht freigegebenes Handle einen Speicherverlust verursachen kann.

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

Das folgende Beispiel zeigt, wie man mithilfe von zuverlässig Handles setzt PrepareConstrainedRegions Methode.Um ein Handle zuverlässig auf ein bestimmtes bereits vorhandenes Handle festzulegen, müssen Sie sicherstellen, dass die Zuweisung des nativen Handles und die anschließende Aufzeichnung dieses Handles innerhalb eines SafeHandle Objekt ist atomar.Jeder Fehler zwischen diesen Vorgängen (z. B. ein Thread-Abbruch oder eine Ausnahme wegen nicht genügend Arbeitsspeicher) führt dazu, dass das native Handle verloren geht.Du kannst den ... benutzen PrepareConstrainedRegions Methode, um sicherzustellen, dass der Griff nicht ausläuft.

So einfach wie:

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

Mich würde interessieren, warum Sie das tun möchten.Es gibt keinen einfachen Weg, dies zu tun, da es sich nicht um eine gute Praxis handelt.Sie sollten wahrscheinlich zu Ihrem Entwurf zurückkehren und einen saubereren Weg finden, um das Endziel zu erreichen.

Ich denke nicht, dass das eine gute Idee ist.Gehen Sie dieses Problem noch einmal an: Versuchen Sie, einen anderen Mechanismus wie gemeinsame Daten zur Signalisierung zwischen Threads zu verwenden.

Wie bei den anderen bin ich mir nicht sicher, ob das eine so gute Idee ist, aber wenn Sie es wirklich tun möchten, können Sie eine Unterklasse von SynchronizationContext erstellen, die das Posten und Senden von Delegaten an den Zielthread ermöglicht (wenn es sich um einen WinForms-Thread handelt). wird für Sie erledigt, da eine solche Unterklasse bereits existiert).Der Zielthread muss jedoch eine Art Nachrichtenpumpenäquivalent implementieren, um die Delegaten zu empfangen.

@Orion Edwards

Ich verstehe Ihren Standpunkt, dass im „finally“-Block eine Ausnahme ausgelöst wird.

Ich denke jedoch, dass es eine Möglichkeit gibt – mithilfe eines weiteren Threads – diese Ausnahme-als-Interrupt-Idee zu nutzen.

Thread A:

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

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

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

Oder ist das einfach albern?

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top