Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/263.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/windows/17.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 有没有办法解决由第三方库引起的OS加载器锁死锁?_C#_Windows_Multithreading_Com_Deadlock - Fatal编程技术网

C# 有没有办法解决由第三方库引起的OS加载器锁死锁?

C# 有没有办法解决由第三方库引起的OS加载器锁死锁?,c#,windows,multithreading,com,deadlock,C#,Windows,Multithreading,Com,Deadlock,我有一个有趣的问题,我在其他任何地方都没有看到记录(至少不是这个特定的问题) 本期是COM、VB6和.NET的结合,使它们能够很好地发挥作用 以下是我所拥有的: 旧式VB6 ActiveX DLL(由我们编写) 一种用C语言编写的多线程Windows服务,通过网络处理来自客户端的请求并发回结果。它通过创建一个新的STA线程来处理每个请求。每个请求处理程序线程实例化一个COM对象(在ActiveX DLL中定义)来处理请求并获得结果(传入一个XML字符串,然后返回一个XML字符串),显式释放CO

我有一个有趣的问题,我在其他任何地方都没有看到记录(至少不是这个特定的问题)

本期是COM、VB6和.NET的结合,使它们能够很好地发挥作用

以下是我所拥有的:

  • 旧式VB6 ActiveX DLL(由我们编写)
  • 一种用C语言编写的多线程Windows服务,通过网络处理来自客户端的请求并发回结果。它通过创建一个新的STA线程来处理每个请求。每个请求处理程序线程实例化一个COM对象(在ActiveX DLL中定义)来处理请求并获得结果(传入一个XML字符串,然后返回一个XML字符串),显式释放COM对象并退出。然后,服务将结果发送回客户端
  • 所有网络代码都使用异步网络(即线程池线程)进行处理
是的,我知道这在一开始就已经是一件冒险的事情了,因为VB6一开始对多线程应用程序不是很友好,但不幸的是,这正是我目前所坚持的

我已经修复了一些导致代码死锁的问题(例如,确保COM对象实际上是从单独的STA线程创建和调用的,确保在线程退出之前显式释放COM对象,以防止垃圾收集器和COM互操作代码之间发生死锁等。),但有一个死锁场景我似乎无法解决

在WinDbg的帮助下,我能够弄清楚发生了什么,但我不确定如何(或是否)解决这个特殊的僵局

发生了什么事

如果一个请求处理程序线程正在退出,而另一个请求处理程序线程正在同时启动,则由于VB6运行时初始化和终止例程的工作方式,可能会发生死锁

死锁发生在以下场景中:

  • 正在启动的新线程正在创建一个新实例(VB6)COM对象来处理传入的请求。此时,COM运行时间处于调用对象类工厂的调用的中间。类工厂实现在VB6运行时本身(<强> MVBVM60.DLL<强>)。。也就是说,它调用VB6运行时的DllGetClassObject函数。这反过来调用内部运行时函数(

    MSVBVM60!CThreadPool::InitRuntime
    ),它获取一个互斥体并进入一个关键部分来完成部分工作。此时,它将调用LoadLibraryoleaut32.dll加载到进程中,同时保持该互斥体。因此,现在它保持该内部VB6运行时互斥体并等待OS加载程序锁定

  • 退出的线程已经在加载程序锁内部运行,因为它已经执行了托管代码,并在<强> Kelnel.ExtTyths/Stult>函数中执行。具体地,它在处理< <代码> DLLthTythyDeTea>代码>消息>强> >MVBVM60.DLL>强>线程上,这又调用这是一种在线程(

    MSVBVM60!CThreadPool::terminaterUnitime
    )上终止VB6运行时的方法。现在,此线程尝试获取另一个正在初始化的线程已经拥有的相同互斥体

一个典型的死锁。线程A有L1并且需要L2,但是线程B有L2并且需要L1

问题是(如果您一直关注我的话)我无法控制VB6运行时在其内部线程初始化和拆卸例程中执行的操作

理论上,如果我可以强制VB6运行时初始化代码在OS加载器锁内运行,我将防止死锁,因为我相当确定VB6运行时持有的互斥锁只在初始化和终止例程中使用

要求

  • 我不能从单个STA线程进行COM调用,因为这样服务将无法处理并发请求。我也不能让长时间运行的请求阻止其他客户端请求。这就是为什么我为每个请求创建一个STA线程

  • 我需要在每个线程上创建COM对象的新实例,因为我需要确保每个实例在VB6代码中都有自己的全局变量副本(VB6为每个线程提供了自己的所有全局变量副本)

我尝试过的解决方案无效

已将ActiveX DLL转换为ActiveX EXE

首先,我尝试了显而易见的解决方案,并创建了一个ActiveX EXE(进程外服务器)来处理COM调用。最初,我编译它,以便为每个传入的请求创建一个新的ActiveX EXE(进程),我还尝试了使用每个对象的线程编译选项(创建一个进程实例,并在ActiveX EXE中的新线程上创建每个对象)

这修复了有关VB6运行时的死锁问题,因为VB6运行时从未正确加载到.NET代码中。但是,这导致了另一个问题:如果并发请求进入服务,ActiveX EXE往往会随机失败,并出现
RPC_E_SERVERFAULT
错误。我假设这是因为COM marshalling和/或VB6运行时无法在ActiveX EXE中处理并发对象创建/销毁或并发方法调用

强制VB6代码在操作系统加载程序锁内运行

下,我切换到使用COM类的ActiveX DLL。为了强制VB6运行时在OS加载锁中运行线程初始化代码,我创建了一个原生(Win32)C++ DLL,在<强> dLMLIGN<强> >中处理代码< > DLL*THEADRADION/<代码> < <代码> DLL*THEADRADION/<代码>代码调用<强>
public class ClientHandler {

    private static ManualResetEvent _safeForNewThread = new ManualResetEvent(true);

    private void HandleRpcRequest(string request)
    {

        Thread rpcThread = new Thread(delegate()
        {
            DbRequestProxy dbRequest = null;

            try
            {
                Thread.BeginThreadAffinity();

                string response = null;

                // Creates a COM object. The VB6 runtime initializes itself here.
                // Other threads can be executing here at the same time without fear
                // of a deadlock, because the VB6 runtime lock is re-entrant.

                dbRequest = new DbRequestProxy();

                // Call the COM object
                response = dbRequest.ProcessDBRequest(request);

                // Send response back to client
                _messenger.Send(Messages.RpcResponse(response), true);
                }
            catch (Exception ex)
            {
                _messenger.Send(Messages.Error(ex.ToString()));
            }
            finally
            {
                if (dbRequest != null)
                {
                    // Force release of COM objects and VB6 globals
                    // to prevent a different deadlock scenario with VB6
                    // and the .NET garbage collector/finalizer threads
                    dbRequest.Dispose();
                }

                // Other request threads cannot start right now, because
                // we're exiting this thread, which will detach the VB6 runtime
                // when the underlying native thread exits

                _safeForNewThread.Reset();
                Thread.EndThreadAffinity();
            }
        });

        // Make sure we can start a new thread (i.e. another thread
        // isn't in the middle of exiting...)

        _safeForNewThread.WaitOne();

        // Put the thread into an STA, start it up, and wait for
        // it to end. If other requests come in, they'll get picked
        // up by other thread-pool threads, so we won't usually be blocking anyone
        // by doing this (although we are blocking a thread-pool thread, so
        // hopefully we don't block for *too* long).

        rpcThread.SetApartmentState(ApartmentState.STA);
        rpcThread.Start();
        rpcThread.Join();

        // Since we've joined the thread, we know at this point
        // that any DLL_THREAD_DETACH notifications have been handled
        // and that the underlying native thread has completely terminated.
        // Hence, other threads can safely be started.

        _safeForNewThread.Set();

    }
}