Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using ClearScript inside Threads #53

Closed
ahoekstra opened this issue May 7, 2018 · 2 comments
Closed

Using ClearScript inside Threads #53

ahoekstra opened this issue May 7, 2018 · 2 comments
Assignees
Labels

Comments

@ahoekstra
Copy link

I have a C# application where I spawn multiple threads. I'm on .NET framework 4.7.1. Inside these threads, work is performed and this work may execute user-defined scripted functions. I am using ClearScript as the scripting engine and for purposes of this question I am using the VBScriptEngine. Here is an sample application demonstrating my problem:

    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"));
    }

On the Evaluate() function I get the following error: System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.'

I understand ClearScript has implemented thread affinity and a spawned thread cannot access the globally defined engine. So what is my alternative? Create a new instance of ClearScript for each new thread? This seems incredibly wasteful and would create a lot of overhead - my application will need to process thousands of threads. I went ahead and tried this approach anyways - and while it does work (for a while) - end up getting an error. Here's a revised version of my sample app:

    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"));
        }
    }

On the new VBScriptEngine() call, I now start getting: System.ComponentModel.Win32Exception: 'Not enough storage is available to process this command'.

I'm not really sure what's causing that message as the application isn't taking up a lot of RAM.

I realize this sample application is starting all the threads at once but my full application ensures only 4 threads are running and I still end up getting this message after a while. I don't know why, but I can't get rid of this message either - even after restarting the app and Visual Studio. A little clarity on what's causing this message would be useful.

So my question is - if I only need, say 4 threads, running at once - is there a way I can just create 4 instances of the VBScriptEngine and reuse it for each new thread call? Or even just 1 instance of the VBScriptEngine on the main thread and each new thread just shares it?

@ClearScriptLib
Copy link
Collaborator

ClearScriptLib commented May 8, 2018

Hello @ahoekstra,

It's difficult to diagnose your issue without more information, so we'll make a couple of general comments and provide a sample that demonstrates concurrent VBScriptEngine usage.

First, creating thousands of threads is almost always counterproductive. You reserve gigabytes of address space just for stacks, and since most machines don't have thousands of cores, you're likely to get thrashing and resource exhaustion. Thread pools optimize efficiency by carefully controlling thread usage.

Unfortunately Windows script engines (JScript/VBScript) have thread affinity, and that makes them fundamentally incompatible with thread pools, as each instance can only run script code on the thread that constructed it.

The Dispatcher class provides a convenient way to deal with thread-bound objects in a thread-pooled environment. Here's a quick sample.

First, create an array of script engines with dedicated threads:

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

Now you can use Dispatcher to invoke the script engines from the thread pool:

var items = Enumerable.Range(0, 10000);
Parallel.ForEach(items, item => {
    var engine = engines[item % engines.Length];
    engine.Dispatcher.Invoke(() => {
        var expr = string.Format("{0} * 2", item);
        Console.WriteLine(engine.Evaluate(expr));
    });
});

This sample uses simple round-robin engine selection and synchronous dispatching, but more sophisticated approaches are certainly possible.

Finally, to terminate the threads and dispose the script engines:

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

Good luck!

@ahoekstra
Copy link
Author

Thank you! This is a great response and appears to have addressed my question perfectly. Now I get to try and implement this solution into my application! Was time to update to Parallel threading anyways..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants