在CLR中使用托管线程和光纤

Way*_*yne 16 c# c++ exception-handling fibers

好的,以下链接警告说讨论使用不受支持和未记录的apis.好吧,我试图以任何方式使用代码示例.它主要起作用.关于以下与例外有关的具体问题的任何想法?

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

仅供参考,我对原始样本做了改进.它维持着指向"previousfiber"的指针.相反,下面更新的示例使用"mainfiber"指针,该指针将传递给每个光纤类.这样,它们总能回到主纤维上.这允许主光纤处理所有其他光纤的调度.其他纤维总是"屈服"回主纤维.

发布此问题的原因与在光纤内抛出异常有关.根据文章,通过使用带有CreateLogicalThreadState(),SwitchOutLogicalThreadState()等的CorBindToRunTime API,框架将为每个光纤创建一个托管线程并正确处理异常.

但是,在所包含的代码示例中,它具有UUnit测试,该测试通过在光纤内抛出托管异常并在同一光纤内捕获它来进行实验.那柔软的作品.但是在通过记录消息处理它之后,堆栈似乎处于错误状态,因为如果光纤调用任何其他方法甚至是空方法,则整个应用程序崩溃.

这对我来说意味着SwitchOutLogicalThreadState()和SwitchInLogicalThreadState()可能没有正确使用,否则他们可能没有做好自己的工作.

注意:问题的一个线索是托管代码注销了Thread.CurrentThread.ManagedThreadId,并且每个光纤都是相同的.这表明CreateLogicalThreadState()方法并没有像宣传的那样真正创建新的托管线程.

为了更好地分析这个,我制作了一个伪代码列表,其中列出了用于处理光纤的低级API的顺序.请记住,光纤都在同一个线程上运行,因此没有任何同时发生的事情,这是一个线性逻辑.当然,必要的技巧是保存和恢复堆栈.这就是它似乎遇到麻烦的地方.

它最初只是一个线程,然后它转换为光纤:

  1. ConvertThreadToFiber(objptr);
  2. CreateFiber()//创建几个win32光纤.

现在第一次调用光纤,它的启动方法执行此操作:

  1. corhost-> SwitchOutLogicalThreadState(饼干); 主cookie保存在堆栈中.
  2. SwitchToFiber(); //第一次调用光纤启动方法
  3. corhost-> CreateLogicalThreadState();
  4. 运行主要的光纤抽象方法.

最终纤维需要回到主纤维:

  1. corhost-> SwitchOutLogicalThreadState(饼干);
  2. SwitchToFiber(纤维);
  3. corhost-> SwitchInLogicalThreadState(饼干); //主纤维饼干,对吗?

主光纤也将恢复预先存在的光纤:

  1. corhost-> SwitchOutLogicalThreadState(饼干);
  2. SwitchToFiber(纤维);
  3. corhost-> SwitchInLogicalThreadState(饼干); //主纤维饼干,对吗?

以下是fibers.cpp,它包装了托管代码的光纤api.

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

}
Run Code Online (Sandbox Code Playgroud)

上面的fibers.cpp类文件是Visaul c ++项目中唯一的类.它使用/ CLR:oldstyle开关构建为具有CLR支持的DLL.

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();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的单元测试给出了以下输出然后严重崩溃:

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.
Run Code Online (Sandbox Code Playgroud)

小智 2

不久前,我遇到了同样的问题 - 我尝试使用 .NET 3.5(后来的 4.0)中的代码片段,但它崩溃了。这说服了我放弃“hacky”解决方案。事实上,.NET 缺少通用的协同例程概念。有些人通过枚举器和yield关键字来模拟协同例程(请参阅http://fxcritic.blogspot.com/2008/05/lightweight-fibrecoroutines.html)。然而,这对我来说有明显的缺点:它不像老式的 Win32 纤维那样直观,并且需要您使用IEnumerable每个协同例程的返回类型。

也许这篇文章:http://msdn.microsoft.com/en-us/vstudio/gg316360对您来说很有趣。微软即将推出一个新的async关键字。社区技术预览版 (CTP) 可供下载。我想应该可以在这些异步扩展之上开发一个干净的协同例程实现。