2011-12-31 13 views
15

Ok, il seguente collegamento ha un avvertimento che la discussione utilizza apis non supportati e non documentati. Beh, sto provando a usare il codice di esempio in qualsiasi modo. Funziona principalmente. Qualche idea sul problema specifico di seguito relativo alle eccezioni?Utilizzo di thread e fibre gestiti in CLR

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

Cordiali saluti, ho fatto un miglioramento rispetto al campione originale. Stava mantenendo un puntatore al "precedentefibre". Invece, l'esempio aggiornato sotto utilizza un puntatore "mainfiber" che viene passato a ogni classe di fibra. In questo modo, ritornano sempre alla fibra principale. Ciò consente alla fibra principale di gestire la pianificazione per tutte le altre fibre. Le altre fibre "restituiscono" sempre alla fibra principale.

Il motivo per la pubblicazione di questa domanda ha a che fare con il lancio di eccezioni all'interno di una fibra. Secondo l'articolo, utilizzando l'API CorBindToRunTime con CreateLogicalThreadState(), SwitchOutLogicalThreadState(), ecc., Il framework creerà un thread gestito per ciascuna fibra e gestirà correttamente le eccezioni.

Tuttavia, negli esempi di codice incluso ha un test UUnit che sperimenta il lancio di un'eccezione gestita all'interno di una fibra e la cattura anche all'interno della stessa fibra. Questo morbido di opere. Ma dopo averlo gestito registrando un messaggio, sembra che lo stack sia in uno stato negativo perché se la fibra chiama qualsiasi altro metodo, anche un metodo vuoto, l'intera applicazione si blocca.

Ciò significa che SwitchOutLogicalThreadState() e SwitchInLogicalThreadState() potrebbero non essere utilizzati correttamente oppure potrebbero non funzionare.

NOTA: un indizio del problema è che il codice gestito disconnette Thread.CurrentThread.ManagedThreadId ed è lo stesso per ogni fibra. Ciò suggerisce che il metodo CreateLogicalThreadState() non ha realmente creato un nuovo thread gestito come pubblicizzato.

Per analizzare questo meglio, ho fatto un elenco pseudocodice dell'ordine delle API di basso livello chiamate per gestire le fibre. Ricorda, che tutte le fibre girano sullo stesso thread in modo che non accada nulla di simile, è una logica lineare. Il trucco necessario, naturalmente, è salvare e ripristinare lo stack. È lì che sembra che ci siano problemi.

inizia come un semplice filo è così allora converte in una fibra:

  1. ConvertThreadToFiber (ObjPtr);
  2. CreateFiber() // crea diverse fibre win32.

Ora richiamare una fibra per la prima volta, è il metodo di avvio fa questo:

  1. corhost-> SwitchOutLogicalThreadState (& cookie); Il cookie principale è tenuto in pila.
  2. SwitchToFiber(); // la prima volta chiama il metodo di avvio della fibra
  3. corhost-> CreateLogicalThreadState();
  4. eseguire il metodo principale di fibra astratta.

Infine la fibra deve cedere indietro alla fibra principale:

  1. corhost-> SwitchOutLogicalThreadState (& cookie);
  2. SwitchToFiber (fibra);
  3. corhost-> SwitchInLogicalThreadState (& cookie); // il biscotto principale in fibra , giusto?

Anche la fibra principale riprenderà una fibra preesistente:

  1. corhost-> SwitchOutLogicalThreadState (& cookie);
  2. SwitchToFiber (fibra);
  3. corhost-> SwitchInLogicalThreadState (& cookie); // il principale cookie in fibra, giusto?

Quanto segue è fiber.cpp che avvolge l'API della fibra per il codice gestito.

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

} 

Il file di classe fiber.cpp sopra è l'unica classe nel progetto C++ di Visaul. È costruito come una DLL con supporto CLR usando/CLR: switch 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 prova di unità di cui sopra dà il seguente risultato e poi si blocca male:

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. 
+1

E quale versione di C#/Fx stai usando? L'originale era già considerato instabile per Fx2 –

+0

Questo è C# 3.5. Quindi è senza speranza? Cerchiamo disperatamente di capire come archiviare e riprendere lo stato del thread per una pianificazione ad alte prestazioni. Ho questa altra domanda che è più generale e spiega il background del perché abbiamo bisogno di questa capacità. Altre idee per soluzioni sono le benvenute! http://stackoverflow.com/questions/8685806/c-sharp-first-class-continuation-via-c-interop-or-some-other-way – Wayne

+3

Un thread SO correlato con alternative: [Esiste una fibra api in .net?] (http://stackoverflow.com/questions/1949051/is-there-a-fiber-api-in-net) –

risposta

2

Un tempo fa, ho sperimentato lo stesso problema - ho cercato di utilizzare il frammento di codice in .NET 3.5 (più tardi su 4.0) e si è schiantato. Questo mi ha convinto a rinunciare alla soluzione "hacky". La verità è che .NET manca un concetto di co-routine generico. Ci sono alcuni tipi che simulano le co-routines dagli enumeratori e la parola chiave yield (vedere http://fxcritic.blogspot.com/2008/05/lightweight-fibercoroutines.html). Tuttavia, questo ha chiari svantaggi: non è così intuitivo da utilizzare come fibre Win32 vecchie e richiede l'uso di IEnumerable come tipo di ritorno per ogni co-routine.

Forse questo articolo: http://msdn.microsoft.com/en-us/vstudio/gg316360 è interessante per te. Microsoft sta per introdurre una nuova parola chiave async. Un'anteprima della tecnologia di comunità (CTP) è disponibile per il download. Immagino che dovrebbe essere possibile sviluppare un'implementazione di co-routine pulita oltre a quelle estensioni asincrone.

+0

Bene, grazie, ma un altro fattore è venuto a giocare. Abbiamo anche bisogno della capacità di avere tutto questo lavoro su AppDomains in modo che i plugin possano essere scaricati/caricati dinamicamente. Sembra quindi che ci sia un codice di esempio Coop Fiber per .Net 2.0 SDK per creare il proprio host C++ personalizzato del CLR e fornire il proprio pool di thread con fibre. Abbiamo in programma di effettuare ricerche e test per farlo nel prossimo futuro. – Wayne

+0

Scratch il mio ultimo commento. Duh! 2.0 è troppo vecchio per affrontare ora. Cosa stavo pensando? La fibra di Coop anche nell'host non è più supportata. Forse async è l'unico modo ... ma causa il cambio di contesto? Dovrà provarlo. – Wayne

0

Quando si utilizzano le fibre, è necessario memorizzare lo stato di stack di gestione delle eccezioni su una variabile locale (nello stack) prima di passare alla fibra principale. La prima operazione subito dopo lo switch (quando l'esecuzione ritorna) sta ripristinando lo stack di eccezioni dal backup in una variabile locale. Date un'occhiata a questo blog su come utilizzare le fibre con Delphi senza rompere la gestione delle eccezioni: http://jsbattig.blogspot.com/2015/03/how-to-properly-support-windows-fibers.html

Il punto è, se si vuole utilizzare fibre e scrivere gestori di eccezioni e Switch fibre dentro e provare, infine, o provare blocco catch , dovrai capire come farlo con CLR.

Sto giocando con Fibre in C# e non ho ancora trovato la strada. Se ci fosse un modo per farlo, immagino che sarà un trucco alla fine della giornata.

Problemi correlati