我有一个使用外部库的控制台应用程序。库坚持始终从同一个线程调用;否则会锁定。 (我确实尝试以STA身份运行,看看是否能解决它 - 但不,它确实坚持你必须始终使用相同的线程。我的猜测是线程本地存储...)
以前,应用程序使用原始TCP连接进行通信。但是,我最近将其更改为使用WCF。现在看来,WCF随机选择线程来运行我的代码,这会导致严重的失败。
我需要绝对100%阻止这种行为发生。我不关心我的代码运行在哪个线程中,只要它总是相同的线程!我花了最近几天在互联网上搜索并反复粉碎我的脑袋,试图让WCF停止使用线程。
我尝试过的事情:
InstanceContextMode.Single
强制WCF为我的东西使用单个对象,这很有用,但不能直接解决问题。
ConcurrencyMode = ConcurrencyMode.Single
保证只有一个线程会同时运行,但不保证哪一个。
UseSynchronizationContext
似乎对任何事情都没有任何影响,正如我所知道的那样。
使用这些标志,我设法达到每个客户端获得单个线程的程度。但这仍然意味着当第一个客户端断开连接并且下一个客户端连接时,我得到一个不同的线程,并且该库挂起了我的程序。
我也尝试过强力方法:我编写了一个创建自己的工作线程的类,并允许您将代码排入队列以在该线程上执行。我孤立地测试了这个类,它似乎工作得很好,但是当我尝试在我的WCF应用程序中使用它时,会发生一些非常奇怪的事情。该程序完美地处理第一个命令,将结果返回给客户端,然后永久挂起。
这种行为完全没有任何意义。我可以从控制台输出中看到它没有卡在外部库中,并且它也没有卡在我的新工作排队类中。那么它到底在哪里被卡住了?!
此时,我通常会开始插入更多的调试打印 - 除了你不能将调试打印插入到WCF中,只能插入它调用的代码。所以我不知道服务主机试图做什么......
我已经看到关于这个主题的各种SO答案,所有这些都说解决方案是完全不同的。有一个讨论“同步上下文”并且或多或少难以理解 - 但它似乎与我的工作队列类做同样的事情。还有另外一个关于设置各种服务标志 - 我已经完成了它并没有解决它。其他人建议实施自己的IOperationBehaviour
(看起来非常复杂)。
基本上在这一点上我不知道该怎么办,我不能让这些东西起作用。 Plz的帮助。 : - (
[控制台应用程序,自托管,NetTcpBinding
,代码中的配置,.NET 4 - 如果重要......]
这是工作队列类,如果重要的话:[它有点大,BTW。]
public sealed class ThreadManager
{
private Thread _thread; // Worker thread.
private volatile Action _action; // Enqueued method.
private volatile object _result; // Method result.
private volatile bool _done; // Has the method finished executing?
public void Start()
{
_action = null;
_result = null;
_done = true;
_thread = new Thread(MainLoop);
_thread.Start();
}
public void ExecuteInWorkerThread(Action action)
{
// Wait for queue to empty...
Monitor.Enter(this); // Lock the object, so we can inspect it.
while (_action != null)
{
Monitor.Pulse(this); // Wake up the next thread waiting on the lock.
Monitor.Wait(this); // Release lock, wait for Pulse(), acquire lock.
}
// Enqueue action...
_action = action;
_done = false;
// Wait for action to complete...
while (! _done)
{
Monitor.Pulse(this); // Wake up the next thread waiting on the lock.
Monitor.Wait(this); // Release lock, wait for Pulse(), acquire lock.
}
// Worker thread has finished doing it's thing now.
Monitor.Pulse(this); // Wake up any threads trying to enqueue work.
Monitor.Exit(this); // Release the lock.
}
public T ExecuteInWorkerThread<T>(Func<T> action)
{
ExecuteInWorkerThread(() => { _result = action(); });
return (T) _result; // If this cast fails, something has gone spectacularly wrong!
}
// Runs forever in worker thread.
private void MainLoop()
{
while (true)
{
// Wait for an action to dequeue...
Monitor.Enter(this); // Lock object so we can inspect it.
while (_action == null)
{
Monitor.Pulse(this); // Wake up the next thread waiting on the lock.
Monitor.Wait(this); // Release lock, wait for Pulse(), acquire lock.
}
// Dequeue action...
var action = _action;
_action = null;
// Perform the action...
action(); // Do the actual action!
_done = true; // Tell the caller we're done.
Monitor.Pulse(this); // Wake the caller up.
Monitor.Exit(this); // Release the lock.
}
}
}
正如我所说的那样,当我单独测试时,看起来效果很好。 [关于多线程编码和确定性的嘀咕。]当在WCF中运行时,它总是在完全相同的点上失败。
答案 0 :(得分:2)
由于ExecuteInWorkerThread
在一个线程上运行所有操作,因此如果以递归方式调用它将阻塞。所以我怀疑你的挂起可能是因为你有一个动作调用库,然后在完成之前通过ExecuteInWorkerThread
再次调用库。
我没有测试你的ThreadManager类;我的直觉说它看起来太复杂了,所以如果你不介意包括Reactive Extensions(nuget package rx-main),那么你可以像这样重构你的ThreadManager类:
public class ThreadManager
{
EventLoopScheduler _scheduler = new EventLoopScheduler();
public T ExecuteInWorkerThread<T>(Func<T> action)
{
return Observable.Start(action, _scheduler).Wait();
}
}
如果您还需要异步调用,也可以将此方法添加到其中:
public Task<T> ExecuteInWorkerThreadAsync<T>(Func<T> action)
{
return Observable.Start(action, _scheduler).ToTask();
}
答案 1 :(得分:2)
在进一步检查时,似乎我忘记了包装一些外部库调用,导致库使服务器死锁。现在我已经解决了这个问题,一切都很完美......
抱歉愚蠢。
答案 2 :(得分:1)
您需要了解SynchronizationContext
概念,尤其是在WCF的上下文中。我强烈建议您阅读一本优秀的Programming WCF Services书籍,详细介绍。但一般来说,您可以创建自己的自定义SynchronizationContext
,为服务调用创建线程关联。在上面提到的书中有一个例子,但你也可以从同一作者on MSDN,线程亲和同步上下文中读到它,这是你的一个例子需要。