Использование управляемых потоков и волокон в 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 в С#, и я еще не мог найти пути. Если бы был способ сделать это, я думаю, что это будет взлом в конце дня.