强制WCF使用一个线程

时间:2013-11-15 11:29:21

标签: c# multithreading wcf

我有一个使用外部库的控制台应用程序。库坚持始终从同一个线程调用;否则会锁定。 (我确实尝试以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中运行时,它总是在完全相同的点上失败。

3 个答案:

答案 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线程亲和同步上下文中读到它,这是你的一个例子需要。