c# - 强制某些代码始终在同一个线程上运行
问题描述
我们有一个旧的第 3 方系统(我们称之为 Junksoft® 95),我们通过 PowerShell 进行交互(它公开了一个 COM 对象),我正在将它包装在一个 REST API(ASP.NET Framework 4.8 和 WebAPI 2)。我使用System.Management.Automation
nuget 包创建了一个PowerShell
我将 Junksoft 的 COM API 实例化为dynamic
然后使用的对象的对象:
//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();
当我在同一个线程上运行所有这些(例如在控制台应用程序中)时,这工作正常。但是,由于某种原因,当我放入a并从我的网络应用程序中的不同控制器使用它时,这通常不起作用。我说通常是因为当 ASP.NET 碰巧将传入调用传递给在其上创建的线程时,这实际上是有效的。如果没有,Junksoft 95 会给我一个错误。junksoftAPI
System.Web.Caching.Cache
junksoftAPI
我有什么办法可以确保所有交互都junksoftAPI
发生在同一个线程上?
请注意,我不想将整个 Web 应用程序变成单线程应用程序!控制器和其他地方的逻辑应该在不同的线程上正常发生。它应该只是 Junksoft 特定线程上发生的 Junksoft 交互,如下所示:
[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 应用程序的代码。
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”的同一上下文线程上执行。
但请注意,虽然所有的 Web 请求都是在独立线程上执行的,但它们最终会等待一些任务在单例工作线程上执行。这最终会在您的 Web 应用程序中造成瓶颈。无论如何,这是您的设计限制。
推荐阅读
- java - 为什么带有令牌的 Java HTTP 请求总是未经授权
- reactjs - .NET Core 2.2 Web API、React 和 Google V8
- html - 在圆形上展开背景
- html - VBA网页抓取:点击可点击的下拉菜单(不悬停)
- java - Drools:比较来自同一对象的 2 个数组列表
- google-apps-script - 粘贴值以用作公式(Google 表格的应用程序脚本)
- google-app-maker - 保存谷歌应用程序制造商发送的电子邮件的网址
- mysql - SQLSTATE[22023]:无效参数值:3037 提供给函数 mbrcontains 的 GIS 数据无效
- python - 总线错误:两个矩阵相乘时为 10
- woocommerce - woocommerce 交易电子邮件:使用 g 套件或 mailgun?建议和经验