c# c++ exception-handling fibers

c# - Usando hilos y fibras gestionados en CLR



c++ exception-handling (3)

De acuerdo, el siguiente enlace tiene una advertencia de que la discusión utiliza apis sin soporte e indocumentadas. Bueno, estoy tratando de usar el código de muestra de cualquier manera. En su mayoría funciona. ¿Alguna idea sobre el tema específico a continuación relacionado con las excepciones?

http://msdn.microsoft.com/en-us/magazine/cc164086.aspx

Para su información, hice una mejora sobre la muestra original. Se mantenía un puntero a la "fibra anterior". En su lugar, el ejemplo actualizado a continuación utiliza un puntero de "mainfiber" que se pasa a cada clase de fibra. De esa manera, siempre devuelven a la fibra principal. Eso permite que la fibra principal maneje la programación de todas las demás fibras. Las otras fibras siempre "ceden" a la fibra principal.

La razón para publicar esta pregunta tiene que ver con lanzar excepciones dentro de una fibra. De acuerdo con el artículo, al usar la API CorBindToRunTime con CreateLogicalThreadState (), SwitchOutLogicalThreadState (), etc., el marco creará un subproceso administrado para cada fibra y manejará las excepciones de manera adecuada.

Sin embargo, en los ejemplos de código incluidos, tiene una prueba UUnit que experimenta lanzando una excepción administrada dentro de una fibra y también atrapándola dentro de la misma fibra. Ese suave de las obras. Pero después de manejarlo registrando un mensaje, parece que la pila está en mal estado porque si la fibra llama a cualquier otro método, incluso a un método vacío, la aplicación falla.

Esto me implica que SwitchOutLogicalThreadState () y SwitchInLogicalThreadState () tal vez no se estén usando correctamente o que no estén haciendo su trabajo.

NOTA: Una pista del problema es que el código administrado cierra la sesión de Thread.CurrentThread.ManagedThreadId y es el mismo para cada fibra. Esto sugiere que el método CreateLogicalThreadState () no creó realmente un nuevo hilo administrado como se anuncia.

Para analizar esto mejor, he hecho una lista de pseudocódigo del orden de las API de bajo nivel llamadas para manejar las fibras. Recuerde, todas las fibras se ejecutan en el mismo hilo, por lo que no sucede nada al mismo tiempo, es una lógica lineal. El truco necesario, por supuesto, es guardar y restaurar la pila. Ahí es donde parece estar teniendo problemas.

Comienza como un simple hilo y luego se convierte en una fibra:

  1. ConvertThreadToFiber (objptr);
  2. CreateFiber () // crea varias fibras win32.

Ahora invoca una fibra la primera vez, su método de inicio hace esto:

  1. corhost-> SwitchOutLogicalThreadState (& cookie); La cookie principal se mantiene en la pila.
  2. SwitchToFiber (); // primera vez llama al método de arranque de fibra
  3. corhost-> CreateLogicalThreadState ();
  4. Ejecutar el método principal de fibra abstracta.

Finalmente, la fibra debe devolver a la fibra principal:

  1. corhost-> SwitchOutLogicalThreadState (& cookie);
  2. SwitchToFiber (fibra);
  3. corhost-> SwitchInLogicalThreadState (& cookie); // la galleta principal de fibra, ¿verdad?

También la fibra principal reanudará una fibra preexistente:

  1. corhost-> SwitchOutLogicalThreadState (& cookie);
  2. SwitchToFiber (fibra);
  3. corhost-> SwitchInLogicalThreadState (& cookie); // la galleta principal de fibra, ¿verdad?

El siguiente es fibres.cpp que envuelve la api de fibra para el código administrado.

#define _WIN32_WINNT 0x400 #using <mscorlib.dll> #include <windows.h> #include <mscoree.h> #include <iostream> using namespace std; #if defined(Yield) #undef Yield #endif #define CORHOST namespace Fibers { typedef System::Runtime::InteropServices::GCHandle GCHandle; VOID CALLBACK unmanaged_fiberproc(PVOID pvoid); __gc private struct StopFiber {}; enum FiberStateEnum { FiberCreated, FiberRunning, FiberStopPending, FiberStopped }; #pragma unmanaged #if defined(CORHOST) ICorRuntimeHost *corhost; void initialize_corhost() { CorBindToCurrentRuntime(0, CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (void**) &corhost); } #endif void CorSwitchToFiber(void *fiber) { #if defined(CORHOST) DWORD *cookie; corhost->SwitchOutLogicalThreadState(&cookie); #endif SwitchToFiber(fiber); #if defined(CORHOST) corhost->SwitchInLogicalThreadState(cookie); #endif } #pragma managed __gc __abstract public class Fiber : public System::IDisposable { public: #if defined(CORHOST) static Fiber() { initialize_corhost(); } #endif Fiber() : state(FiberCreated) { void *objptr = (void*) GCHandle::op_Explicit(GCHandle::Alloc(this)); fiber = ConvertThreadToFiber(objptr); mainfiber = fiber; //System::Console::WriteLine( S"Created main fiber."); } Fiber(Fiber *_mainfiber) : state(FiberCreated) { void *objptr = (void*) GCHandle::op_Explicit(GCHandle::Alloc(this)); fiber = CreateFiber(0, unmanaged_fiberproc, objptr); mainfiber = _mainfiber->fiber; //System::Console::WriteLine(S"Created worker fiber"); } __property bool get_IsRunning() { return state != FiberStopped; } int GetHashCode() { return (int) fiber; } bool Resume() { if(!fiber || state == FiberStopped) { return false; } if( state == FiberStopPending) { Dispose(); return false; } void *current = GetCurrentFiber(); if(fiber == current) { return false; } CorSwitchToFiber(fiber); return true; } void Dispose() { if(fiber) { void *current = GetCurrentFiber(); if(fiber == current) { state = FiberStopPending; CorSwitchToFiber(mainfiber); } state = FiberStopped; System::Console::WriteLine( S"/nDeleting Fiber."); DeleteFiber(fiber); fiber = 0; } } protected: virtual void Run() = 0; void Yield() { CorSwitchToFiber(mainfiber); if(state == FiberStopPending) throw new StopFiber; } private: void *fiber, *mainfiber; FiberStateEnum state; private public: void main() { state = FiberRunning; try { Run(); } catch(System::Object *x) { System::Console::Error->WriteLine( S"/nFIBERS.DLL: main Caught {0}", x); } Dispose(); } }; void fibermain(void* objptr) { //System::Console::WriteLine( S"/nfibermain()"); System::IntPtr ptr = (System::IntPtr) objptr; GCHandle g = GCHandle::op_Explicit(ptr); Fiber *fiber = static_cast<Fiber*>(g.Target); g.Free(); fiber->main(); System::Console::WriteLine( S"/nfibermain returning"); } #pragma unmanaged VOID CALLBACK unmanaged_fiberproc(PVOID objptr) { #if defined(CORHOST) corhost->CreateLogicalThreadState(); #endif fibermain(objptr); #if defined(CORHOST) corhost->DeleteLogicalThreadState(); #endif } }

El archivo de clase fibres.cpp anterior es la única clase en el proyecto Visaul c ++. Se construye como una DLL con soporte para CLR usando el modificador / CLR: oldstyle.

using System; using System.Threading; using Fibers; using NUnit.Framework; namespace TickZoom.Utilities { public class FiberTask : Fiber { public FiberTask() { } public FiberTask(FiberTask mainTask) : base(mainTask) { } protected override void Run() { while (true) { Console.WriteLine("Top of worker loop."); try { Work(); } catch (Exception ex) { Console.WriteLine("Exception: " + ex.Message); } Console.WriteLine("After the exception."); Work(); } } private void Work() { Console.WriteLine("Doing work on fiber: " + GetHashCode() + ", thread id: " + Thread.CurrentThread.ManagedThreadId); ++counter; Console.WriteLine("Incremented counter " + counter); if (counter == 2) { Console.WriteLine("Throwing an exception."); throw new InvalidCastException("Just a test exception."); } Yield(); } public static int counter; } [TestFixture] public class TestingFibers { [Test] public void TestIdeas() { var fiberTasks = new System.Collections.Generic.List<FiberTask>(); var mainFiber = new FiberTask(); for( var i=0; i< 5; i++) { fiberTasks.Add(new FiberTask(mainFiber)); } for (var i = 0; i < fiberTasks.Count; i++) { Console.WriteLine("Resuming " + i); var fiberTask = fiberTasks[i]; if( !fiberTask.Resume()) { Console.WriteLine("Fiber " + i + " was disposed."); fiberTasks.RemoveAt(i); i--; } } for (var i = 0; i < fiberTasks.Count; i++) { Console.WriteLine("Disposing " + i); fiberTasks[i].Dispose(); } } } }

La prueba de la unidad anterior da el siguiente resultado y luego falla gravemente:

Resuming 0 Top of worker loop. Doing work on fiber: 476184704, thread id: 7 Incremented counter 1 Resuming 1 Top of worker loop. Doing work on fiber: 453842656, thread id: 7 Incremented counter 2 Throwing an exception. Exception: Just a test exception. After the exception.


Cuando use fibras, debe almacenar el estado de la pila de administración de excepciones en una variable local (en la pila) antes de cambiar a su fibra principal. La primera operación inmediatamente después del cambio (when execution comes back) es restaurar la pila de excepciones desde su copia de seguridad en una variable local. Eche un vistazo a esta entrada de blog sobre cómo usar fibras con Delphi sin interrumpir el manejo de excepciones: http://jsbattig.blogspot.com/2015/03/how-to-properly-support-windows-fibers.html

El punto es que, si desea utilizar Fibers AND escribir controladores de excepciones Y cambiar las fibras en el interior e intentar finalmente el bloque try-catch, deberá descubrir cómo hacerlo con CLR.

Estoy jugando con Fibres en C # y todavía no pude encontrar el camino. Si hubiera una manera de hacerlo, me imagino que será un hack al final del día.


Hace un tiempo, experimenté el mismo problema: intenté usar el fragmento de código en .NET 3.5 (más adelante en 4.0) y se bloqueó. Esto me convenció de alejarme de la solución "hacky". La verdad es que a .NET le falta un concepto genérico de co-rutina. Hay algunos tipos que simulan co-rutinas de los enumeradores y la palabra clave de yield (consulte http://fxcritic.blogspot.com/2008/05/lightweight-fibercoroutines.html ). Sin embargo, esto tiene claras desventajas: no es tan intuitivo de usar como las viejas fibras de Win32 y requiere que use IEnumerable como tipo de retorno para cada co-rutina.

Quizás este artículo: http://msdn.microsoft.com/en-us/vstudio/gg316360 sea ​​interesante para usted. Microsoft está a punto de introducir una nueva palabra clave async . Se ofrece una descarga de tecnología comunitaria (CTP) para descargar. Supongo que debería ser posible desarrollar una implementación de co-rutina limpia sobre esas extensiones asíncronas.


Puede usar Delphi coroutines framework https://github.com/Purik/AIO Ha completado la implementación de Fibras.

Por ejemplo, puede ajustar el procedimiento anónimo a Fiber. El procedimiento se ejecutará en el contexto de Fiber, puede acceder y detectar cualquier excepción provocada en Fiber