[英]Using managed threads and fibers in CLR
好的,以下链接警告说讨论使用不受支持和未记录的apis。 好吧,我试图以任何方式使用代码示例。 它主要起作用。 关于以下与例外有关的具体问题的任何想法?
http://msdn.microsoft.com/en-us/magazine/cc164086.aspx
仅供参考,我对原始样本做了改进。 它维持着指向“previousfiber”的指针。 相反,下面更新的示例使用“mainfiber”指针,该指针将传递给每个光纤类。 这样,它们总能回到主纤维上。 这允许主光纤处理所有其他光纤的调度。 其他纤维总是“屈服”回主纤维。
发布此问题的原因与在光纤内抛出异常有关。 根据文章,通过使用带有CreateLogicalThreadState(),SwitchOutLogicalThreadState()等的CorBindToRunTime API,框架将为每个光纤创建一个托管线程并正确处理异常。
但是,在所包含的代码示例中,它具有UUnit测试,该测试通过在光纤内抛出托管异常并在同一光纤内捕获它来进行实验。 那柔软的作品。 但是在通过记录消息处理它之后,堆栈似乎处于错误状态,因为如果光纤调用任何其他方法甚至是空方法,则整个应用程序崩溃。
这对我来说意味着SwitchOutLogicalThreadState()和SwitchInLogicalThreadState()可能没有正确使用,否则他们可能没有做好自己的工作。
注意:问题的一个线索是托管代码注销了Thread.CurrentThread.ManagedThreadId,并且它对于每个光纤都是相同的。 这表明CreateLogicalThreadState()方法并没有像宣传的那样真正创建新的托管线程。
为了更好地分析这一点,我制作了一个伪代码列表,其中列出了用于处理光纤的低级API的顺序。 请记住,光纤都在同一个线程上运行,因此没有任何同时发生的事情,这是一个线性逻辑。 当然,必要的技巧是保存和恢复堆栈。 这就是它似乎遇到麻烦的地方。
它最初只是一个线程,然后它转换为光纤:
现在第一次调用光纤,它的启动方法执行此操作:
最终纤维需要回到主纤维:
主光纤也将恢复预先存在的光纤:
以下是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
}
}
上面的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();
}
}
}
}
上面的单元测试给出了以下输出然后严重崩溃:
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.
前一段时间,我遇到了同样的问题 - 我试图在.NET 3.5(后来的4.0版)中使用代码片段并且它崩溃了。 这说服我放弃了“hacky”解决方案。 事实是,.NET缺少一个通用的常规概念。 有些人通过枚举器和yield
关键字来模拟协同例程(请参阅http://fxcritic.blogspot.com/2008/05/lightweight-fibercoroutines.html )。 但是,这对我来说有明显的缺点:它不像使用旧的Win32光纤那样直观,它要求你使用IEnumerable
作为每个协同例程的返回类型。
也许这个arcticle: http : //msdn.microsoft.com/en-us/vstudio/gg316360对你来说很有意思。 微软即将推出一个新的async
关键字。 提供社区技术预览(CTP)供下载。 我想应该可以在这些异步扩展之上开发一个干净的协同例程实现。
使用光纤时,必须在切换到主光纤之前将异常管理堆栈状态存储在本地变量(堆栈上)上。 切换后的第一个操作(when execution comes back)
是从本地变量中的备份恢复异常堆栈。 看看这篇关于如何在不破坏异常处理的情况下使用Delphi的博客条目: http : //jsbattig.blogspot.com/2015/03/how-to-properly-support-windows-fibers.html
关键是,如果你想使用Fibers AND编写异常处理程序并在内部切换光纤并尝试finally或try-catch块,你将不得不弄清楚如何使用CLR执行此操作。
我正在使用C#中的Fibers,我找不到方法。 如果有办法做到这一点,我想在一天结束时它将成为一个黑客。
你可以使用Delphi coroutines框架https://github.com/Purik/AIO它已经完成了Fibers的实现。
例如,您可以将匿名过程包装到Fiber- procedure将在Fiber上下文中运行,您可以访问和检测Fiber中引发的任何异常
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.