Использование управляемых потоков и волокон в CLR
Хорошо, следующая ссылка имеет предупреждение о том, что в обсуждении используется неподдерживаемый и недокументированный apis. Ну, я пытаюсь использовать образец кода любым способом. Это в основном работает. Любые идеи по поводу конкретной проблемы, относящиеся к исключениям?
http://msdn.microsoft.com/en-us/magazine/cc164086.aspx
FYI, я улучшил исходный образец. Он поддерживал указатель на "предыдущее волокно". Вместо этого в обновленном примере ниже используется указатель "mainfiber", который передается каждому классу волокна. Таким образом, они всегда возвращаются к основному волокну. Это позволяет основному волокну обрабатывать планирование для всех других волокон. Другие волокна всегда "возвращаются" к основному волокну.
Причина публикации этого вопроса связана с бросанием исключений внутри волокна. Согласно статье, используя API CorBindToRunTime с CreateLogicalThreadState(), SwitchOutLogicalThreadState() и т.д., Структура создаст управляемый поток для каждого волокна и правильно обработает исключения.
Однако в приведенных примерах кода у него есть тест UUnit, который экспериментирует с бросанием управляемого исключения в Fiber и также улавливает его внутри одного и того же волокна. Это мягкое произведение. Но после обработки сообщения путем записи сообщения кажется, что стек находится в плохом состоянии, потому что если волокно вызывает какой-либо другой метод даже пустым методом, все приложение аварийно завершает работу.
Это означает, что SwitchOutLogicalThreadState() и SwitchInLogicalThreadState(), возможно, не используются должным образом или, возможно, они не выполняют свою работу.
ПРИМЕЧАНИЕ. Один ключ к проблеме заключается в том, что управляемый код выводит файл Thread.CurrentThread.ManagedThreadId, и он одинаковый для каждого волокна. Это говорит о том, что метод CreateLogicalThreadState() действительно не создавал новый управляемый поток, как рекламируется.
Чтобы проанализировать это лучше, я сделал список псевдокодов порядка API низкого уровня, который называется для обработки волокон. Помните, что все волокна работают на одной и той же ветке, так что между ними ничего не происходит, это линейная логика. Разумеется, необходимым средством является сохранение и восстановление стека. Что там, где, похоже, возникают проблемы.
Он начинается как просто поток, поэтому он преобразуется в волокно:
- ConvertThreadToFiber (ObjPtr);
- CreateFiber()//создаем несколько волокон win32.
Теперь вызовите волокно в первый раз, он запускает метод:
- corhost- > SwitchOutLogicalThreadState (& печенья); Основной файл cookie
хранящихся в стеке.
- SwitchToFiber();//первый раз вызывает метод запуска волокна
- corhost- > CreateLogicalThreadState();
- запустить абстрактный абстрактный метод.
В конце концов волокно должно вернуться к основному волокну:
- corhost- > SwitchOutLogicalThreadState (& печенья);
- SwitchToFiber (волокно);
- corhost- > SwitchInLogicalThreadState (& печенья);//основное волокно
cookie, правильно?
Также основное волокно возобновит существовавшее ранее волокно:
- corhost- > SwitchOutLogicalThreadState (& печенья);
- SwitchToFiber (волокно);
- corhost- > SwitchInLogicalThreadState (& печенья);//основной файл cookie, правильно?
Ниже приведена информация о fib.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
}
}
Вышеупомянутый файл класса styles.cpp является единственным классом в проекте Visaul С++. Он построен как DLL с поддержкой CLR с использованием /CLR: старост-переключатель.
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();
}
}
}
}
Вышеупомянутый unit test дает следующий результат, а затем сильно падает:
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
Некоторое время назад у меня возникла такая же проблема - я попытался использовать фрагмент кода в .NET 3.5 (позже 4.0), и он разбился. Это убеждало меня отвернуться от "хакерского" решения. Правда в том, что .NET не хватает общей концепции совместной программы. Есть несколько парней, которые имитируют совлокальные подпрограммы с помощью счетчиков и ключевое слово yield
(см. http://fxcritic.blogspot.com/2008/05/lightweight-fibercoroutines.html). Тем не менее, это имеет для меня явные недостатки: это не так просто, как использовать старые добрые волокна Win32, и для этого требуется использовать IEnumerable
как возвращаемый тип для каждой совместной процедуры.
Может быть, эта статья: http://msdn.microsoft.com/en-us/vstudio/gg316360 вам интересна. Microsoft собирается представить новое ключевое слово async
. Предварительный просмотр сообщества (CTP) предлагается для загрузки. Я предполагаю, что должно быть возможно разработать чистую совместную реализацию поверх этих асинхронных расширений.
Ответ 2
При использовании волокон вы должны сохранить состояние стека управления исключениями в локальной переменной (в стеке), прежде чем переключаться на основное волокно.
Первая операция сразу после переключения (когда выполнение возвращается) восстанавливает стек исключений из резервной копии в локальной переменной.
Взгляните на эту запись в блоге о том, как использовать волокна с Delphi, не нарушая обработку исключений: http://jsbattig.blogspot.com/2015/03/how-to-properly-support-windows-fibers.html
Дело в том, что если вы хотите использовать Fibers И писать обработчики исключений И переключаться внутри волокон и пытаться наконец или попытаться блокировать catch, вам придется выяснить, как это сделать с CLR.
Я играю с Fibers в С#, и я еще не мог найти пути. Если бы был способ сделать это, я думаю, что это будет взлом в конце дня.