C# 强制某些代码始终在同一线程上运行

C# 强制某些代码始终在同一线程上运行,c#,asp.net,multithreading,powershell,asp.net-web-api2,C#,Asp.net,Multithreading,Powershell,Asp.net Web Api2,我们有一个旧的第三方系统(我们称之为Junksoft®95),我们通过PowerShell与之接口(它公开一个COM对象),我正在将其包装到REST API(ASP.NET Framework 4.8和WebAPI 2)中。我使用System.Management.Automationnuget包创建一个PowerShell,在其中我将Junksoft的COM API实例化为一个动态对象,然后使用: //I'm omitting some exception handling and maint

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

//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();

如果你没有说这是一个第三方工具,我会说这是一个GUI类。出于实际原因,让多个线程对其进行写入是一个非常糟糕的主意。NET强制执行严格的“只有创建线程才应写入”规则

一般来说,Web服务器,特别是ASP.Net使用了相当大的线程池。我们讨论的是每个核心10到100个线程。这意味着很难将任何请求落实到特定的线程中。你最好不要尝试


同样,查看GUI类可能是最好的选择。您基本上可以创建一个线程,其唯一目的是模拟GUI的事件队列。普通Windows窗体应用程序的主/UI线程负责创建每个GUI类实例。它通过轮询/处理事件队列保持活动状态。当它通过事件队列接收到cancel命令时,它仅以X结束。分派只是将订单放入该队列,因此我们可以避免交叉线程问题。

如果您没有说这是第三方工具,我会说它是一个GUI类。出于实际原因,让多个线程对其进行写入是一个非常糟糕的主意。NET强制执行严格的“只有创建线程才应写入”规则

一般来说,Web服务器,特别是ASP.Net使用了相当大的线程池。我们讨论的是每个核心10到100个线程。这意味着很难将任何请求落实到特定的线程中。你最好不要尝试


同样,查看GUI类可能是最好的选择。您基本上可以创建一个线程,其唯一目的是模拟GUI的事件队列。普通Windows窗体应用程序的主/UI线程负责创建每个GUI类实例。它通过轮询/处理事件队列保持活动状态。当它通过事件队列接收到cancel命令时,它仅以X结束。分派只是将订单放入该队列,因此我们可以避免交叉线程问题。

您可以创建自己的单例工作线程来实现这一点。下面是您可以将其插入web应用程序的代码

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();
        }
    }
}
公共类JunkSoftRunner
{
私有静态JunkSoftRunner_实例;
//singleton模式,用于限制仅在单个线程上执行的所有操作。
公共静态JunkSoftRunner实例=>\u实例??(\u实例=new JunkSoftRunner());
私有只读信号量limu信号量;
私有只读自动存储事件_newtaskrunsil;
专用TaskCompletionSource\u TaskCompletionSource;
私有函数(Func);;
私人JunkSoftRunner()
{
_信号量=新信号量lim(1,1);
_newtaskrunsical=新的自动重置事件(false);
var contextThread=新线程(ThreadLooper)
{
优先级=线程优先级。最高
};
contextThread.Start();
}
私有void ThreadLooper()
{
while(true)
{
//等待,直到收到下一个任务信号。
_newtaskrunsial.WaitOne();
//接收到下一个任务执行信号。
尝试
{
//尝试执行任务并获得结果
var result=_func.Invoke();
//任务已成功执行,请设置结果
_taskCompletionSource.SetResult(结果);
}
捕获(例外情况除外)
{
//任务执行引发异常,设置异常并继续使用循环器
_taskCompletionSource.SetException(ex);
}
}
}
公共异步任务运行(Func Func,CancellationToken CancellationToken=default(CancellationToken))
{
//一次只允许运行一个线程。
wait_信号量.WaitAsync(cancellationToken);
//线程已获取信号量并输入
尝试
{
//创建新的任务完成源,以等待在上下文线程上执行func
_taskCompletionSource=新的taskCompletionSource();
//设置上下文线程要执行的函数
_func=()=>func();
//向正在等待的上下文线程发出执行任务的信号
_newtaskrunsical.Set();
//等待并返回结果,直到在上下文/循环器线程上完成任务执行。
return(TResult)wait_taskCompletionSource.Task;
}
最后
{
//释放信号量以允许其他线程获取它。
_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;
                });
            });
        }
    }   
}
公共类程序
{
//测试垃圾软流道
公共静态void Main()
{
//获取singleton实例
var softRunner=JunkSoftRunner.Instance;
//在不同线程上模拟web请求
对于(变量i=0;i<10;i++)
{
var=i;
//在新线程上启动web请求。
Task.Run(异步()=>
{
WriteLine($“任务{taskIndex}(线程ID:{Thread.CurrentThread.ManagedThreadId})”已启动);
返回等待softRunner。运行(()=>
{
WriteLine($“->Task{taskIndex}在{Thread.CurrentThread.ManagedThreadId}线程上完成。”);
返回任务索引;
});
});
}
}   
}
输出:

注意,尽管函数是从di启动的
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;
                });
            });
        }
    }   
}
public class JunksoftSTA : IDisposable
{
    private readonly BlockingCollection<Action<Lazy<dynamic>>> _pump;
    private readonly Thread _thread;

    public JunksoftSTA()
    {
        _pump = new BlockingCollection<Action<Lazy<dynamic>>>();
        _thread = new Thread(() =>
        {
            var lazyApi = new Lazy<dynamic>(() =>
            {
                var powerShell = System.Management.Automation.PowerShell.Create();
                powerShell.AddScript("Add-Type -Path C:\Path\To\Junksoft.dll");
                powerShell.AddScript("New-Object Com.Junksoft.ScriptingObject");
                dynamic junksoftAPI = powerShell.Invoke()[0];
                return junksoftAPI;
            });
            foreach (var action in _pump.GetConsumingEnumerable())
            {
                action(lazyApi);
            }
        });
        _thread.SetApartmentState(ApartmentState.STA);
        _thread.IsBackground = true;
        _thread.Start();
    }

    public Task<T> CallAsync<T>(Func<dynamic, T> function)
    {
        var tcs = new TaskCompletionSource<T>(
            TaskCreationOptions.RunContinuationsAsynchronously);
        _pump.Add(lazyApi =>
        {
            try
            {
                var result = function(lazyApi.Value);
                tcs.SetResult(result);
            }
            catch (Exception ex)
            {
                tcs.SetException(ex);
            }
        });
        return tcs.Task;
    }

    public Task CallAsync(Action<dynamic> action)
    {
        return CallAsync<object>(api => { action(api); return null; });
    }

    public void Dispose() => _pump.CompleteAdding();

    public void Join() => _thread.Join();
}
// A static field stored somewhere
public static readonly JunksoftSTA JunksoftStatic = new JunksoftSTA();

await JunksoftStatic.CallAsync(api => { api.Login("x", "y"); });
int age = await JunksoftStatic.CallAsync(api => api.GetAgeByCustomerId(custId));