强制某些代码始终在同一线程上运行 [英] Forcing certain code to always run on the same thread

查看:112
本文介绍了强制某些代码始终在同一线程上运行的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们有一个旧的第三方系统(我们称其为Junksoft®95),可以通过PowerShell与之交互(公开一个COM对象),并且我正在将其包装在REST API(ASP.NET Framework)中4.8和WebAPI 2).我使用 System.Management.Automation nuget包创建一个 PowerShell ,在其中将Junksoft的COM API实例化为一个 dynamic 对象,然后使用该对象:

We have an old 3rd party system (let's call it Junksoft® 95) that we interface with via PowerShell (it exposes a COM object) and I'm in the process of wrapping it in a REST API (ASP.NET Framework 4.8 and WebAPI 2). I use the System.Management.Automation nuget package to create a PowerShell in which I instantiate Junksoft's COM API as a dynamic object that I then use:

//I'm omitting some exception handling and maintenance code for brevity
powerShell = System.Management.Automation.PowerShell.Create();
powerShell.AddScript("Add-Type -Path C:\Path\To\Junksoft\Scripting.dll");
powerShell.AddScript("New-Object Com.Junksoft.Scripting.ScriptingObject");
dynamic junksoftAPI = powerShell.Invoke()[0];

//Now we issue commands to junksoftAPI like this:
junksoftAPI.Login(user,pass);
int age = junksoftAPI.GetAgeByCustomerId(custId);
List<string> names = junksoftAPI.GetNames();

当我在同一线程(例如,在控制台应用程序中)上运行所有这些命令时,此方法工作正常.但是,由于某些原因,当我将 junksoftAPI 放入 System.Web.Caching.Cache 并以不同的方式使用它时,通常不起作用我的网络应用中的控制器.我之所以通常说 ,是因为当ASP.NET恰好将传入代码提供给创建 junksoftAPI 的线程的调用时,这实际上是可行的.如果没有,那么Junksoft 95会给我一个错误.

This works fine when I run all of this on the same thread (e.g. in a console application). However, for some reason this usually doesn't work when I put junksoftAPI into a System.Web.Caching.Cache and use it from different controllers in my web app. I say ususally because this actually works when ASP.NET happens to give the incoming call to the thread that junksoftAPI was created on. If it doesn't, Junksoft 95 gives me an error.

我有什么办法确保与 junksoftAPI 的所有交互都发生在相同线程上?

Is there any way for me to make sure that all interactions with junksoftAPI happen on the same thread?

注意,我不想将整个Web应用程序变成单线程应用程序!控制器和其他地方的逻辑应该像正常情况那样在不同的线程上发生.只能是Junksoft特定线程上发生的Junksoft交互,如下所示:

Note that I don't want to turn the whole web application into a single-threaded application! The logic in the controllers and elswhere should happen like normal on different threads. It should only be the Junksoft interactions that happen on the Junksoft-specific thread, something like this:

[HttpGet]
public IHttpActionResult GetAge(...)
{
    //finding customer ID in database...

    ...

    int custAge = await Task.Run(() => {
        //this should happen on the Junksoft-specific thread and not the next available thread
        var cache = new System.Web.Caching.Cache();
        var junksoftAPI = cache.Get(...); //This has previously been added to cache on the Junksoft-specific thread
        return junksoftAPI.GetAgeByCustomerId(custId);
    });

    //prepare a response using custAge...
}

推荐答案

您可以创建自己的单例工作线程来实现此目的.这是您可以将其插入Web应用程序中的代码.

You can create your own singleton worker thread to achieve this. Here is the code which you can plug it into your web application.

public class JunkSoftRunner
{
    private static JunkSoftRunner _instance;

    //singleton pattern to restrict all the actions to be executed on a single thread only.
    public static JunkSoftRunner Instance => _instance ?? (_instance = new JunkSoftRunner());

    private readonly SemaphoreSlim _semaphore;
    private readonly AutoResetEvent _newTaskRunSignal;

    private TaskCompletionSource<object> _taskCompletionSource;
    private Func<object> _func;

    private JunkSoftRunner()
    {
        _semaphore = new SemaphoreSlim(1, 1);
        _newTaskRunSignal = new AutoResetEvent(false);
        var contextThread = new Thread(ThreadLooper)
        {
            Priority = ThreadPriority.Highest
        };
        contextThread.Start();
    }

    private void ThreadLooper()
    {
        while (true)
        {
            //wait till the next task signal is received.
            _newTaskRunSignal.WaitOne();

            //next task execution signal is received.
            try
            {
                //try execute the task and get the result
                var result = _func.Invoke();

                //task executed successfully, set the result
                _taskCompletionSource.SetResult(result);
            }
            catch (Exception ex)
            {
                //task execution threw an exception, set the exception and continue with the looper
                _taskCompletionSource.SetException(ex);
            }

        }
    }

    public async Task<TResult> Run<TResult>(Func<TResult> func, CancellationToken cancellationToken = default(CancellationToken))
    {
        //allows only one thread to run at a time.
        await _semaphore.WaitAsync(cancellationToken);

        //thread has acquired the semaphore and entered
        try
        {
            //create new task completion source to wait for func to get executed on the context thread
            _taskCompletionSource = new TaskCompletionSource<object>();

            //set the function to be executed by the context thread
            _func = () => func();

            //signal the waiting context thread that it is time to execute the task
            _newTaskRunSignal.Set();

            //wait and return the result till the task execution is finished on the context/looper thread.
            return (TResult)await _taskCompletionSource.Task;
        }
        finally
        {
            //release the semaphore to allow other threads to acquire it.
            _semaphore.Release();
        }
    }
}

控制台主要测试方法:

public class Program
{
    //testing the junk soft runner
    public static void Main()
    {
        //get the singleton instance
        var softRunner = JunkSoftRunner.Instance;

        //simulate web request on different threads
        for (var i = 0; i < 10; i++)
        {
            var taskIndex = i;
            //launch a web request on a new thread.
            Task.Run(async () =>
            {
                Console.WriteLine($"Task{taskIndex} (ThreadID:'{Thread.CurrentThread.ManagedThreadId})' Launched");
                return await softRunner.Run(() =>
                {
                    Console.WriteLine($"->Task{taskIndex} Completed On '{Thread.CurrentThread.ManagedThreadId}' thread.");
                    return taskIndex;
                });
            });
        }
    }   
}

输出:

请注意,尽管该功能是从不同的线程启动的,但是某些代码始终总是在ID为"5"的同一上下文线程上执行.

Notice that, though the function was launched from the different threads, some portion of code got always executed always on the same context thread with ID: '5'.

但是请注意,尽管所有Web请求都在独立线程上执行,但它们最终将等待某些任务在单例工作线程上执行.这最终将在您的Web应用程序中造成瓶颈.无论如何,这是您的设计限制.

But beware that, though all the web requests are executed on independent threads, they will eventually wait for some tasks to get executed on the singleton worker thread. This will eventually create a bottle neck in your web application. This is anyway your design limitation.

这篇关于强制某些代码始终在同一线程上运行的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆