Использование управляемых потоков и волокон в CLR - PullRequest
16 голосов
/ 31 декабря 2011

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

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 удерживается в стеке.
  2. SwitchToFiber (); // первый раз вызывает метод запуска волокна
  3. corhost-> CreateLogicalThreadState ();
  4. запустить метод абстрактного основного волокна.

В конечном итоге волокно должно возвращаться к основному волокну:

  1. corhost-> SwitchOutLogicalThreadState (& печенье);
  2. corhost-> SwitchInLogicalThreadState (& печенье); // основное волокно печенье, верно?

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

  1. corhost-> SwitchOutLogicalThreadState (& печенья);
  2. SwitchToFiber (волокно); * * тысяча пятьдесят-три
  3. corhost-> SwitchInLogicalThreadState (& печенье); // основное волокно cookie, верно?

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

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

}

Приведенный выше файл 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<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.

Ответы [ 3 ]

2 голосов
/ 05 января 2012

Некоторое время назад я столкнулся с той же проблемой - я попытался использовать фрагмент кода в .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). Я полагаю, что на основе этих асинхронных расширений можно было бы разработать чистую сопрограммную реализацию.

0 голосов
/ 21 мая 2018

Вы можете использовать платформу Delphi coroutines https://github.com/Purik/AIO Она завершила реализацию Fibers.

Например, вы можете обернуть анонимную процедуру в Fiber - процедура будет запущена в контексте Fiber, вы можете получить доступ иобнаружить любое исключение, возникшее в Fiber

0 голосов
/ 18 марта 2016

При использовании волокон необходимо сохранить состояние стека управления исключениями в локальной переменной (в стеке), прежде чем переключаться на основное волокно. Первая операция сразу после переключателя (when execution comes back) восстанавливает стек исключений из вашей резервной копии в локальной переменной. Посмотрите на эту запись в блоге о том, как использовать волокна с Delphi без прерывания обработки исключений: http://jsbattig.blogspot.com/2015/03/how-to-properly-support-windows-fibers.html

Дело в том, что если вы хотите использовать Fibers И писать обработчики исключений И переключать волокна внутри и пытаться выполнить finally или блок try-catch, вам придется выяснить, как это сделать с CLR.

Я играю с Fibers в C # и пока не могу найти выход. Если бы был способ сделать это, я думаю, что в конце дня это будет хак.

...