Использование ClearScript внутри потоков - PullRequest
0 голосов
/ 07 мая 2018

У меня есть приложение на C #, в котором я создаю несколько потоков. Я на .NET Framework 4.7.1. Внутри этих потоков выполняется работа, и эта работа может выполнять пользовательские скриптовые функции. Я использую ClearScript в качестве механизма сценариев, и для целей этого вопроса я использую VBScriptEngine. Вот пример приложения, демонстрирующего мою проблему:

    static VBScriptEngine vbsengine = new VBScriptEngine();

    static void Main(string[] args)
    {
        for (int i=0;i<4000;i++)
        {
            Thread t = new Thread(Program.ThreadedFunc);
            t.Start(i);
        }
        Console.ReadKey();
    }

    static void ThreadedFunc(object i)
    {
        Console.WriteLine(i + ": " + vbsengine.Evaluate("1+1"));
    }

В функции Evaluate () появляется следующая ошибка: System.InvalidOperationException: 'Вызывающий поток не может получить доступ к этому объекту, поскольку он принадлежит другому потоку.'

Я понимаю, что ClearScript реализовал сходство потоков, и порожденный поток не может получить доступ к глобально определенному движку. Так какая у меня альтернатива? Создать новый экземпляр ClearScript для каждого нового потока? Это кажется невероятно расточительным и создаст много накладных расходов - моему приложению нужно будет обрабатывать тысячи потоков. Я все равно попробовал этот подход - и пока он работает (какое-то время) - в итоге получаю ошибку. Вот пересмотренная версия моего примера приложения:

    static void Main(string[] args)
    {
        for (int i=0;i<4000;i++)
        {
            Thread t = new Thread(Program.ThreadedFunc);
            t.Start(i);
        }
        Console.ReadKey();
    }

    static void ThreadedFunc(object i)
    {
        using (VBScriptEngine vbsengine = new VBScriptEngine())
        {
            Console.WriteLine(i + ": " + vbsengine.Evaluate("1+1"));
        }
    }

При вызове new VBScriptEngine () я начинаю получать: System.ComponentModel.Win32Exception: «Недостаточно памяти для обработки этой команды».

Я не совсем уверен, что вызывает это сообщение, поскольку приложение не занимает много ОЗУ.

Я понимаю, что этот пример приложения запускает все потоки одновременно, но мое полное приложение обеспечивает работу только 4 потоков, и я все равно получаю это сообщение через некоторое время. Я не знаю почему, но я не могу избавиться и от этого сообщения - даже после перезапуска приложения и Visual Studio. Небольшая ясность в том, что вызывает это сообщение, было бы полезно.

Итак, мой вопрос - если мне нужно, скажем, 4 потока, работающих одновременно, - могу ли я просто создать 4 экземпляра VBScriptEngine и повторно использовать его для каждого вызова нового потока? Или даже только один экземпляр VBScriptEngine в основном потоке, и каждый новый поток просто делится им?

Ответы [ 2 ]

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

С некоторой помощью от команды ClearScript я смог заставить работать мой пример приложения, используя только 1 выделенный экземпляр движка на поток. Хитрость заключалась в том, чтобы сначала создать все необходимые движки с собственным потоком, а затем в моем цикле, используя Dispatcher.Invoke () для вызова моей многопоточной функции. Вот обновленный пример приложения, использующего этот подход:

    static void Main(string[] args)
    {
        var vbengines = new VBScriptEngine[Environment.ProcessorCount];
        var checkPoint = new ManualResetEventSlim();
        for (var index = 0; index < vbengines.Length; ++index)
        {
            var thread = new Thread(indexArg =>
            {
                using (var engine = new VBScriptEngine())
                {
                    vbengines[(int)indexArg] = engine;
                    checkPoint.Set();
                    Dispatcher.Run();
                }
            });
            thread.Start(index);
            checkPoint.Wait();
            checkPoint.Reset();
        }

        Parallel.ForEach(Enumerable.Range(0, 4000), item => {
            var engine = vbengines[item % vbengines.Length];

            engine.Dispatcher.Invoke(() => {
                ThreadedFunc(new myobj() { vbengine = engine, index = item });
            });
        });

        Array.ForEach(vbengines, engine => engine.Dispatcher.InvokeShutdown());

        Console.ReadKey();            
    }

    static void ThreadedFunc(object obj)
    {            
        Console.WriteLine(((myobj)obj).index.ToString() + ": " + ((myobj)obj).vbengine.Evaluate("1+1").ToString());
    }

    class myobj
    {
        public VBScriptEngine vbengine { get; set; }
        public int index { get; set; }
    }
0 голосов
/ 07 мая 2018

Оператор using позволяет сборщику мусора (GC) автоматически высвобождать память, выделенную для управляемого объекта, когда этот объект больше не используется, поэтому вы не получите накладных расходов.

Для первого выпуска необходимо использовать lock оператор , который позволяет гарантировать, что один поток не входит в критическую секцию, в то время как другой поток находится в критической секции кода:

    private static readonly object locked = new object();

    static void ThreadedFunc(object i)
    {
        // Only one thread will execute this code
        lock (locked)
        {                
            //  When you exit the Using block, the `VBScriptEngine` is going to be disposed
            using (VBScriptEngine vbsengine = new VBScriptEngine())
            {
                Console.WriteLine(i + ": " + vbsengine.Evaluate("1+1"));                    
            }
        }
    } 

И если вы хотите установить предел, вы можете попробовать это:

        Parallel.For(0, 4000, new ParallelOptions
        {
            MaxDegreeOfParallelism = 4 // Thread limit
        }, i =>
          {
              // In this case you don't need to use lock statement, it's already thread safe
              ThreadedFunc(i);
         });
...