Force WCF to use one thread

耗尽温柔 提交于 2020-01-29 18:38:26

问题


I have a console application which uses an external library. The library insists on always being called from the same thread; it locks up otherwise. (I did try running as STA to see if that would fix it - but no, it really insists you have to always use the same thread. My guess is thread-local storage...)

Previously the application communicated using a raw TCP connection. However, I recently changed it to use WCF. Now it seems that WCF chooses threads at random to run my code, which results in spectacular failure.

I need to absolutely 100% prevent this behaviour from ever happening. I don't care which thread my code runs in, so long as it is always the same thread! I've spent the last few days scouring the face of the Internet and repeatedly smashing my head into the keyboard in a desperate attempt to make WCF stop using threads.

Things I've tried:

  • InstanceContextMode.Single forces WCF to use a single object for my stuff, which is useful, but doesn't directly address the problem.

  • ConcurrencyMode = ConcurrencyMode.Single guarantees that only one thread will be running concurrently, but doesn't promise which one.

  • UseSynchronizationContext seems to not have any effect on anything, as best as I can tell.

Using these flags, I managed to get to the point where each client gets a single thread. But that still means that when the first client disconnects and the next client connects, I get a different thread and the library hangs my program.


I also tried the brute-force approach: I wrote a class that creates its own worker thread and lets you enqueue code to execute on that thread. I tested the class in isolation, and it appears to work perfectly, but when I try to use it in my WCF application, something extremely strange happens. The program processes the first command perfectly, returns a result to the client, and then hangs forever.

This behaviour makes absolutely no sense whatsoever. I can see from the console output that it isn't stuck in the external library, and it isn't stuck in my new work-queueing class either. So where the hell has it got stuck?!

At this point, I would normally start inserting more debug prints - except you can't insert debug prints into WCF, only the code that it calls. So I cannot tell what the service host is trying to do...


I've seen various SO answers on this topic, all of which say that the solution is something completely different. There's one that talks about "synchronisation contexts" and is more or less incomprehensible - but it appears it would do the same thing as my work queue class. There's another about setting various service flags - which I've already done and it didn't fix it. Somebody else suggested implementing your own IOperationBehaviour (which looks insanely complicated).

Basically at this point I'm not sure what the hell to do, and I can't get this stuff to work. Plz help. :-(

[Console application, self-hosted, NetTcpBinding, configuration in code, .NET 4 - in case it matters...]


Here is the work-queue class, in case it matters: [It's a tad large, 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.
        }
    }
}

As I said, when I test this in isolation, it appears to work just fine. [Mutter something about multithreaded coding and determinism.] When run in WCF, it always fails at exactly the same point.


回答1:


Since ExecuteInWorkerThread runs all actions on one thread, it will block if called recursively. So I suspect your hang maybe because you have an action calling the library, which then calls the library again via ExecuteInWorkerThread before completing.

I've not tested your ThreadManager class; my gut says it looks too complicated though, so as an aside, if you don't mind including Reactive Extensions (nuget package rx-main), then you can refactor your ThreadManager class like this:

public class ThreadManager
{
    EventLoopScheduler _scheduler = new EventLoopScheduler();    

    public T ExecuteInWorkerThread<T>(Func<T> action)
    {
        return Observable.Start(action, _scheduler).Wait();
    }
}

And you can add this method to it if you need an async call too:

public Task<T> ExecuteInWorkerThreadAsync<T>(Func<T> action)
{
    return Observable.Start(action, _scheduler).ToTask();
}



回答2:


On further examination, it seems that I forget to wrap some of the external library calls, causing the library to deadlock the server. Now that I've fixed that, everything works perfectly...

Sorry for being dumb.




回答3:


You need to understand SynchronizationContext concept, especially in the context of the WCF. I strongly recommend to read an excellent Programming WCF Services book, where it is described in details. But generally speaking, you can create your own custom SynchronizationContext that creates thread affinity for service calls. There is an example of it in the above mentioned book, but you can read about it also from the same author on MSDN, section Thread Affinity Synchronization Context, which is an example of exactly what you need.



来源:https://stackoverflow.com/questions/20000078/force-wcf-to-use-one-thread

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!