Использование управляемых потоков и волокон в CLR

Хорошо, следующая ссылка содержит предупреждение о том, что в обсуждении используются неподдерживаемые и недокументированные API. Ну, я все равно пытаюсь использовать образец кода. В основном это работает. Есть какие-нибудь идеи по поводу приведенной ниже конкретной проблемы, связанной с исключениями?

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

К вашему сведению, я сделал улучшение по сравнению с исходным образцом. Он поддерживал указатель на «предыдущий волокно». Вместо этого в обновленном примере ниже используется указатель mainfiber, который передается каждому классу волокна. Таким образом, они всегда уступают основному волокну. Это позволяет основному волокну управлять расписанием для всех остальных волокон. Остальные волокна всегда «уступают» основному волокну.

Причина публикации этого вопроса связана с выбросом исключений внутри фибры. Согласно статье, используя API CorBindToRunTime с CreateLogicalThreadState (), SwitchOutLogicalThreadState () и т. Д., Фреймворк создаст управляемый поток для каждого волокна и должным образом обрабатывает исключения.

Однако во включенных примерах кода есть тест UUnit, который экспериментирует с генерированием управляемого исключения в волокне, а также с его перехватом в том же волокне. Этот софт работает. Но после обработки его путем регистрации сообщения кажется, что стек находится в плохом состоянии, потому что, если волокно вызывает любой другой метод, даже пустой метод, все приложение вылетает.

Это означает, что SwitchOutLogicalThreadState () и SwitchInLogicalThreadState (), возможно, не используются должным образом или, возможно, они не выполняют свою работу.

ПРИМЕЧАНИЕ. Одним из ключей к разгадке проблемы является то, что управляемый код регистрирует Thread.CurrentThread.ManagedThreadId, и он одинаков для всех волокон. Это говорит о том, что метод CreateLogicalThreadState () на самом деле не создавал новый управляемый поток, как было объявлено.

Чтобы лучше это проанализировать, я составил список псевдокода порядка низкоуровневых API-интерфейсов, вызываемых для обработки волокон. Помните, что все волокна работают в одном потоке, поэтому ничего не происходит одновременно, это линейная логика. Необходимая уловка, конечно же, состоит в том, чтобы сохранить и восстановить стек. Вот где у него, кажется, проблемы.

Он начинается как простой поток, а затем преобразуется в волокно:

  1. ConvertThreadToFiber (objptr);
  2. CreateFiber () // создает несколько волокон Win32.

Теперь вызовите волокно в первый раз, его метод запуска делает следующее:

  1. corhost-> SwitchOutLogicalThreadState (& cookie); Основной файл cookie держится в стеке.
  2. SwitchToFiber (); // первый раз вызывает метод запуска волокна
  3. corhost-> CreateLogicalThreadState ();
  4. запускает основной абстрактный метод волокна.

В конце концов волокно должно уступить место основному волокну:

  1. corhost-> SwitchOutLogicalThreadState (& cookie);
  2. SwitchToFiber (волокно);
  3. corhost-> SwitchInLogicalThreadState (& cookie); // основное волокно cookie, верно?

Также основное волокно возобновит существующее волокно:

  1. corhost-> SwitchOutLogicalThreadState (& cookie);
  2. SwitchToFiber (fiber);
  3. corhost-> SwitchInLogicalThreadState (& cookie); // основной файл cookie волокна, не так ли?

Далее следует файл fiber.cpp, который обертывает API-интерфейс волокна для управляемого кода.

#define _WIN32_WINNT 0x400

#using 
#include 
#include 
#include 
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(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
}

}

Вышеупомянутый файл класса fiber.cpp является единственным классом в проекте Visaul c ++. Он построен как DLL с поддержкой CLR с использованием переключателя / CLR: 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();
            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.

16
задан Sathyajith Bhat 19 June 2012 в 11:29
поделиться