Pregunta

El código que quiero escribir es así:

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

Sé que puedo hacer que el subproceso B verifique periódicamente, de manera segura para subprocesos, para ver si el subproceso A ha establecido un indicador, pero eso hace que el código sea más complicado.¿Existe algún mecanismo mejor que pueda utilizar?

Aquí hay un ejemplo más detallado de verificación periódica:

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.
    }
}
¿Fue útil?

Solución

Esta NO es una buena idea

Este artículo habla sobre la biblioteca de tiempo de espera de Ruby. que arroja excepciones entre subprocesos.

Explica cómo hacer tal cosa es fundamentalmente malo.No solo está roto en Ruby, está roto en cualquier lugar que genere excepciones en los subprocesos.

En pocas palabras, lo que puede suceder (y sucede) es esto:

Hilo A:

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

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

Su ejemplo de 'verificación periódica' está bien, ya que en realidad no está generando excepciones entre subprocesos.
Simplemente estás configurando una bandera que dice "lanzar una excepción la próxima vez que mires esta bandera", lo cual está bien ya que no sufre el problema de "se puede lanzar en medio de tu captura o finalmente bloquear".
Sin embargo, si va a hacer eso, también puede configurar un indicador "exitnow" y usarlo para ahorrarse la molestia de crear el objeto de excepción.Un bool volátil funcionará bien para eso.

Otros consejos

Hay suficientes problemas con excepciones que pueden generarse en subprocesos mediante otros mecanismos, como abortar subprocesos y similares, por lo que debería encontrar otra forma de hacerlo.

Una excepción es un mecanismo utilizado para señalar que un proceso ha experimentado algo excepcional que no puede afrontar.Debe intentar evitar escribir el código para que se utilice una excepción para señalar que algo más ha experimentado algo excepcional.

Lo más probable es que ese otro hilo no sepa cómo manejar la excepción. en todos los casos en los que su código podría arrojarlo.

En resumen, debería encontrar algún otro mecanismo para abortar sus subprocesos además del uso de excepciones.

Utilice objetos de evento o similares para indicarle a un hilo que cancele su procesamiento, esa es la mejor manera.

Mientras investigaba otro tema, encontré este artículo que me recordó tu pregunta:

Sondeando las profundidades de ThreadAbortException usando Rotor

Muestra los giros por los que pasa .NET para implementar Thread.Abort(); presumiblemente, cualquier otra excepción entre subprocesos tendría que ser similar.(¡Sí!)

Lo que dice Orion Edwards no es del todo cierto:no es la "única" manera.

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

Usando CER (Regiones de ejecución restringidas) en C# le permite liberar sus recursos como una operación atómica, protegiendo su código de excepciones entre subprocesos.Esta técnica la utilizan varias clases de .NET Framework que funcionan con la API nativa de Windows, donde un identificador no publicado puede provocar una pérdida de memoria.

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

El siguiente ejemplo muestra cómo configurar identificadores de manera confiable utilizando el PrepareConstrainedRegions método.Para establecer de manera confiable un identificador para un identificador preexistente específico, debe asegurarse de que la asignación del identificador nativo y el registro posterior de ese identificador dentro de un SafeHandle El objeto es atómico.Cualquier falla entre estas operaciones (como una cancelación de subproceso o una excepción de falta de memoria) resultará en la pérdida del identificador nativo.Puedes usar el PrepareConstrainedRegions método para asegurarse de que el mango no tenga fugas.

Tan simple como:

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

Me interesa saber por qué querrías hacer esto.No hay una manera fácil de hacerlo, porque no es una buena práctica.Probablemente deberías volver a tu diseño y encontrar una forma más limpia de lograr el objetivo final.

No creo que sea una buena idea..Haga otra solución a este problema: intente utilizar algún otro mecanismo, como datos compartidos, para enviar señales entre subprocesos.

Al igual que los demás, no estoy seguro de que sea una buena idea, pero si realmente quieres hacerlo, puedes crear una subclase de SynchronizationContext que permita publicar y enviar delegados al hilo de destino (si es un hilo de WinForms, el trabajo se hace por usted ya que dicha subclase ya existe).Sin embargo, el hilo de destino tendrá que implementar algún tipo de bomba de mensajes equivalente para recibir a los delegados.

@Orión Edwards

Entiendo su punto acerca de que se lanza una excepción en el bloque finalmente.

Sin embargo, creo que hay una manera (usando otro hilo más) de utilizar esta idea de excepción como interrupción.

Hilo A:

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

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

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

¿O es simplemente una tontería?

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top