Search code examples
c#multithreadingiisfinalizer

Thread Affinity in Critical Finalizer


We're working with a 3rd-party legacy system that requires thread affinity for some of the tear-down logic. We're also hosting a WCF service inside IIS which, under heavy loads will do a rude unloading of our app domain. In these cases it falls to the critical finalizer to do cleanup. Unfortunately, without thread affinity in the finalizer, the 3rd-party system deadlocks.

So roughly:

public class FooEnvironment : CriticalFinalizerObject, IDisposable
{
  public FooEnvironment()
  {
    // start up C API
  }

  public bool Dispose()
  {
    // shutdown C API (from same thread ctor was called on)
  }

  ~FooEnvironment()
  {
    // try to shutdown C API but deadlock!
  }
}

I've tried various things where we Run with the ExecutionContext from the initializing thread, but this doesn't work (at least in IIS) and we get an invalid operation exception stating that this execution context can't be used (ostensibly because it may have been marashalled across AppDomains, which seems likely).

I've read several things basically stating that what I'm trying to do can't be done but I figured I would ask since there isn't a lot of information on this topic.


Solution

  • Back in the old days I developed a library that wrapped the hideous DDEML which is a Win32 api wrapper around the DDE protocol. The DDEML has thread affinity requirements as well so I feel your pain.

    The only strategy that is going to work is to create a dedicate thread that executes all of your library calls. This means biting the bullet and marshaling every single request to call into this API onto this dedicated thread and then marshaling back the result to the original thread. It sucks and its slow, but it is the only method guaranteed to work.

    It can be done, but it is painful. You can see how I tackled the problem in my NDde library. Basically, the finalizer will simply post a message via static method calls to a thread that can accept and dispatch them to the appropriate API call. In my case I created a thread that called Application.Run to listen for messages because DDE required a Windows message loop anyway. In your case you will want to create the thread in a manner that monitors a custom message queue. This is not terribly difficult if you use the BlockingCollection class because the Take method blocks until an item appears the queue.