2011-12-31 13 views
15

Okay, poniższy link zawiera ostrzeżenie, że dyskusja używa nieudokumentowanych i nieudokumentowanych apisów. Próbuję użyć próbki kodu w dowolny sposób. To głównie działa. Wszelkie pomysły dotyczące określonego problemu poniżej dotyczące wyjątków?Używanie zarządzanych wątków i włókien w CLR

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

FYI, zrobiłem poprawę w stosunku do oryginalnej próbki. Utrzymywał wskaźnik do "poprzedniego włókna". Zamiast tego zaktualizowana próbka poniżej wykorzystuje wskaźnik "mainfiber", który zostaje przekazany do każdej klasy światłowodu. W ten sposób zawsze wracają do głównego włókna. To pozwala głównemu światłu na planowanie harmonogramowania dla wszystkich innych włókien. Pozostałe włókna zawsze "oddają" z powrotem do głównego włókna.

Powód umieszczenia tego pytania wiąże się z rzucaniem wyjątków we włókno. Zgodnie z tym artykułem, używając CorBindToRunTime API z CreateLogicalThreadState(), SwitchOutLogicalThreadState(), itp., Framework stworzy zarządzany wątek dla każdego włókna i poprawnie obsłuży wyjątki.

Jednak w załączonych przykładach kodu posiada on test UUnit, który eksperymentuje z wyrzucaniem zarządzanego wyjątku w obrębie Fibre i przechwytywaniem go w tym samym włóknie. Ta miękka robota. Ale po obsłużeniu go przez zalogowanie komunikatu wydaje się, że stos jest w złym stanie, ponieważ jeśli światłowód wywoła dowolną inną metodę, nawet pustą, cała aplikacja ulega awarii.

To sugeruje, że SwitchOutLogicalThreadState() i SwitchInLogicalThreadState() być może nie są używane poprawnie, a może nie wykonują swojej pracy.

UWAGA: Jedna wskazówka dotycząca problemu polega na tym, że zarządzany kod wylogowuje wątek.CurrentThread.ManagedThreadId i jest taki sam dla każdego włókna. Sugeruje to, że metoda CreateLogicalThreadState() tak naprawdę nie utworzyła nowego zarządzanego wątku zgodnie z reklamą.

Aby to lepiej przeanalizować, utworzyłem pseudokodową listę kolejności interfejsów API niskiego poziomu, które są używane do obsługi włókien. Pamiętaj, że wszystkie włókna biegną na tej samej nici, więc nic się nie dzieje, jest to logika liniowa. Oczywiście koniecznym trikiem jest zapisanie i przywrócenie stosu. Tam wydaje się, że ma kłopoty.

Zaczyna się po prostu jako gwint tak następnie konwertuje się do włókna:

  1. ConvertThreadToFiber (objptr);
  2. CreateFiber() // utwórz kilka włókien win32.

Teraz powołać włókno po raz pierwszy, jest to metoda uruchamiania robi to:

  1. corhost-> SwitchOutLogicalThreadState (& cookie); Główny plik cookie to przechowywany na stosie.
  2. SwitchToFiber(); // po raz pierwszy wywołuje metodę uruchamiania włókien:
  3. corhost-> CreateLogicalThreadState();
  4. uruchomić główną metodę abstrakcyjnego włókna.

końcu włókna musi wytworzeniem do głównego włókna:

  1. corhost-> SwitchOutLogicalThreadState (& cookie);
  2. SwitchToFiber (światłowód);
  3. corhost-> SwitchInLogicalThreadState (& cookie); // główny plik cookie z włókna, , prawda?

także głównym włókno będzie wznowić zarejestrowanej wcześniej surowcowy:

  1. corhost-> SwitchOutLogicalThreadState (& cookie);
  2. SwitchToFiber (światłowód);
  3. corhost-> SwitchInLogicalThreadState (& cookie); // główny plik cookie z włókna, prawda?

Poniżej przedstawiono plik fibers.cpp, który otacza włókno api dla kodu zarządzanego.

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

} 

Powyższy plik klasy fibers.cpp jest jedyną klasą w projekcie Visa C++. Jest zbudowany jako DLL z obsługą CLR przy użyciu/CLR: oldstyle switch.

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

Powyższe badanie jednostka daje następujące dane wyjściowe, a następnie zawiesza się źle:

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

Jakiej wersji C#/Fx używasz? Oryginał został już uznany za niestabilny dla Fx2 –

+0

To jest C# 3.5. Czy to jest beznadziejne? Chcielibyśmy dowiedzieć się, jak zapisać i wznowić stan wątku, aby zapewnić wysoką wydajność planowania. Mam inne pytanie, które jest bardziej ogólne i wyjaśnia, dlaczego potrzebujemy tej możliwości. Wszelkie inne pomysły na rozwiązania są mile widziane! http://stackoverflow.com/questions/8685806/c-sharp-first-class-continuation-via-c-interop-us-some-other-way – Wayne

+3

Związany wątek SO z alternatywami: [Czy w aplikacji dostępne jest włókno api .net?] (http://stackoverflow.com/questions/1949051/is-there-a-fiber-api-in-net) –

Odpowiedz

2

Czas temu, przeżyłem ten sam problem - próbowałem użyć fragmentu kodu w .NET 3.5 (później na 4.0) i rozbił się. To przekonało mnie do odwrócenia się od "hacky" rozwiązania. Prawda jest taka, że ​​.NET brakuje ogólnej, rutynowej koncepcji. Jest kilka facetów, które symulują współprogramy przez moduły wyliczające i słowo kluczowe yield (patrz http://fxcritic.blogspot.com/2008/05/lightweight-fibercoroutines.html). Ma to jednak dla mnie wyraźne wady: nie jest tak intuicyjne w użyciu, jak staromodne włókna Win32 i wymaga użycia IEnumerable jako typu zwrotnego dla każdego z rutynowych czynności.

Może ten artykuł: http://msdn.microsoft.com/en-us/vstudio/gg316360 jest interesujący dla Ciebie. Microsoft wkrótce wprowadzi nowe słowo kluczowe async. Do pobrania jest dostępny podgląd technologii społeczności (CTP). Sądzę, że powinno być możliwe stworzenie czystej, współrutynowej implementacji, oprócz tych asynchronicznych rozszerzeń.

+0

Cóż, dziękuję, ale przyszedł kolejny czynnik. Musimy również mieć możliwość wykonywania wszystkich tych czynności w AppDomains, aby wtyczki mogły być ładowane/ładowane dynamicznie. Wygląda więc na to, że istnieje przykładowy kod Coop Fibre dla pakietu .Net 2.0 SDK, aby zbudować własny niestandardowy host języka C++ w CLR i dostarczyć własną pulę wątków z włóknami. Planujemy to zbadać i przetestować w najbliższej przyszłości. – Wayne

+0

Zdrap mój ostatni komentarz. Duh! 2.0 jest za stary, by sobie z tym poradzić. Co ja sobie myślałem? Włókno Coop nawet na hoście nie jest już obsługiwane. Może asynchronizacja jest jedyną metodą ... ale czy powoduje przełączanie kontekstu? Będzie musiał to przetestować. – Wayne

0

Podczas korzystania z włókien należy zachować stan stosu zarządzania wyjątkami na zmiennej lokalnej (na stosie) przed przełączeniem na główne włókno. Pierwsza operacja zaraz po przełączniku (po powrocie wykonania) przywraca stos wyjątków z kopii zapasowej w zmiennej lokalnej. Spójrz na tego wpisu na blogu na temat korzystania z włókna z Delphi bez przerywania obsługi wyjątków: http://jsbattig.blogspot.com/2015/03/how-to-properly-support-windows-fibers.html

Chodzi o to, czy chcesz korzystać z włókien i pisać programy obsługi wyjątku i przełączania włókien wewnątrz i spróbować wreszcie lub spróbuj blok catch , będziesz musiał dowiedzieć się, jak to zrobić z CLR.

Gram z Fibres w C# i nie mogłem znaleźć jeszcze drogi. Jeśli istnieje sposób, aby to zrobić, wyobrażam sobie, że będzie to hack pod koniec dnia.

Powiązane problemy