2008-09-04 9 views
11

El código que quiero escribir es como esto:¿Hay un buen método en C# para lanzar una excepción en un subproceso dado

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 tener hilo B comprobar periódicamente, en forma segura hilo , para ver si el hilo A ha establecido una bandera, pero eso hace que el código sea más complicado. ¿Hay algún mecanismo mejor que pueda usar?

Aquí es un ejemplo más de pulpa de comprobació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. 
    } 
} 
+0

Oh, wow, si eso era posible, usted debe esperar que sea posible ver a incluso una simple adición de una excepción a ser lanzado. – Dykam

Respuesta

10

Esto no es una buena idea

This article talks about ruby's timeout library. que arroja excepciones a través de hilos.

Explica cómo hacer algo así se rompe fundamentalmente. No solo está roto en rubí, está roto en cualquier lugar que arroje excepciones entre los hilos.

En pocas palabras, lo que puede (y hace) pasar es esto:

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

Su ejemplo de control periódico 'está bien, en realidad no estás lanzando excepciones a través de los hilos.
Usted solo está poniendo una bandera que dice "lanzar una excepción la próxima vez que mire esta bandera", lo cual está bien ya que no sufre el "puede ser lanzado en el medio de su captura o finalmente bloquear" problema.
Sin embargo, si va a hacer eso, también puede simplemente establecer una marca "exitnow", y usar eso y ahorrarse la molestia de crear el objeto de excepción. Un bool volátil funcionará bien para eso.

10

Hay bastantes problemas con las excepciones que pueden ser lanzadas en las roscas por otros mecanismos, como abortar hilos y los gustos, que debe encontrar otro forma de hacerlo.

Una excepción es un mecanismo utilizado para indicar que un proceso ha experimentado algo excepcional que no puede resolver. Debería intentar evitar escribir el código para que se use una excepción para indicar que algo más ha experimentado algo excepcional.

Ese otro hilo probablemente no sepa cómo manejar la excepción en todos los casos donde podría ser lanzado por su código.

En resumen, debe encontrar algún otro mecanismo para abortar sus hilos que usar excepciones.

Usar objetos de evento o similares para decirle a un hilo que aborte su procesamiento, esa es la mejor manera.

0

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

0

No creo que sea una buena idea ... Resuelva otro problema: intente utilizar otro mecanismo como datos compartidos para señalar entre hilos.

0

Como 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 WinForms dice que el trabajo está hecho para ti, ya que una subclase ya existe). Sin embargo, el hilo de destino deberá implementar algún tipo de bomba de mensaje equivalente para recibir a los delegados.

0

@Orion Edwards

que tome su punto acerca de una excepción que es lanzado en el bloque finally.

Sin embargo, creo que hay una manera, usando otro hilo, de usar esta idea de excepción como interrupción.

Tema A:

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

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

Tema 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 que simplemente tonto?

2

Durante la investigación de otro tema, me encontré con este artículo que me recordaba a su pregunta:

Plumbing the Depths of the ThreadAbortException using Rotor

Muestra los giros que atraviesa .NET para implementar Thread.Abort() - presumiblemente cualquier otra excepción de hilos cruzados debería ser similar. (¡Yeech!)

1

Lo que Orion Edwards dice 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 

El uso de CER (restringidos Regiones de ejecución) en C# le permite liberar sus recursos como una operación atómica, la protección de su código de excepciones entre hilos. Esta técnica es utilizada por varias clases de .NET Framework que funcionan con la API nativa de Windows, donde un identificador inédito puede causar 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 de forma fiable las manijas usando el método PrepareConstrainedRegions. Para establecer de forma confiable un identificador a un identificador preexistente especificado, debe asegurarse de que la asignación del identificador nativo y la posterior grabación de ese identificador dentro de un objeto SafeHandle sea atómica. Cualquier falla entre estas operaciones (como un aborto de un hilo o una excepción de falta de memoria) dará como resultado el escape del identificador nativo. Puede utilizar el método PrepareConstrainedRegions para asegurarse de que no se haya filtrado el identificador.

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