COM、STA和多线程

4
我有一个遗留的第三方COM DLL。我将其注册到注册表中,并在我的.NET/C#控制台应用程序中添加了RCW。注册表显示COM的线程模型为单元(Apartment)。我的整个应用程序的目的是使用多线程并发地向该COM服务器提交多个请求并接收响应。
我正在使用SmartThreadPool来管理线程,在每个线程中,我都会创建新的对象来执行COM查找/请求提交步骤。然而,如果我查看COM服务器日志,仍然可以看到请求按顺序被提交/处理。
问题出在哪里呢?
所以,我认为问题在于COM被配置为单元(Apartment)/STA。
但我的最终目标是让它正常工作,所以我的问题是:
1. 假设我手头只有这个STA COM DLL文件,是否有任何解决办法可以让它并行处理我的请求?
2. 考虑到这样一个事实,当我并行运行两个实例的控制台应用程序时,最终服务器机器上的日志实际上显示两个实例的请求在两个不同的会话中并行处理。因此,最终应用程序已准备好支持并行处理。(我猜这是一个幼稚的问题)假设我能够拿到COM代码,那么将其支持MTA会容易吗?
[这真的太混乱了,我都快疯了!请注意,每个线程都会从COM DLL文件中创建自己的一组新对象,用于COMServer查找、请求、提交等。]

应用程序代码

public class start
{
    public static void Main(string[] args)
    {
         StartProcessing();
    }

    private static void StartProcessing()
    {
        CoreProcessor pcr = new CoreProcessor();
        pcr.start();
    }
}

public class CoreProcessor
{
    public static ManualResetEvent IsAllDone;
    public static int NumberOfActiveThreads;
    private SmartThreadPool TPool = new SmartThreadPool();
    public void start()
    {
        IEnumerable<string> LstRequests = FileIO.GetAllRequestFileNames();
        NumberOfActiveThreads = LstRequests.Count();
        IsAllDone = new ManualResetEvent(false);
        foreach(var reqName in LstRequests)
        {
            ReqInfo req = new ReqInfo(){RequestPath = reqName;};
            TPool.QueueWorkItem(new WorkItemCallBack(req.ProcessRequest));
        }
        if(NumberOfActiveThreads  > 0)
            IsAllDone.WaitOne();
    }
}

public class ReqInfo
{
    public string RequestPath;
    public void ProcessRequest()
    {
         ABC_COM_Request req = new ABC_COM_XMLUTIL().CreateRequest();
         ABC_COM_Server svr = new ABC_COM_ServerLookup().lookup("serverhostname", 1099);
         ABC_COM_Response resp = svr.submit(req);
         if (InterLocked.Decrement(ref CoreProcessor.NumberOfActiveThreads) == 0)
                     CoreProcessor.IsAllDone.Set();
    }
}

13
"Apartment" 意味着 COM 服务器不支持线程。从工作线程调用的所有函数调用都会被 COM 自动转发到创建该 COM 对象的线程,以确保其在线程安全的情况下使用。你将无法获得并发性。对于此问题没有任何解决方法,不支持线程的代码无法变成支持线程的代码。根据你之前的问题,你已经知道了这个问题。 - Hans Passant
Hans,一如既往地非常清晰和精确,谢谢。但实际上我第一次使用com,所以很困惑。我猜COM本身是在一个非托管进程中执行的,我们通过代理(RCW)来使用它。因此,当多个线程使用相同的COM时,您的意思是我们的代理仍然会发送并行调用,非托管COM进行同步吗? - EagerToLearn
3
你的客户好像对COM的工作原理存在误解;他们可能会像你一样从多个线程提交多个调用,但是这些调用都会被序列化并在STA所在的线程中依次调用。COM子系统会自动为您完成此操作,而且您无法避免它(除非严重破坏该子系统)。 - casperOne
2
你可以通过拥有许多进程,每个进程都有一个STA线程来实现并发。这可能是VC++和JAVA客户端正在做的事情。 - GTG
@GTG - "你可以通过拥有许多进程,每个进程都有一个STA线程来实现并发。" - 也可以通过拥有一个单一进程,具有许多STA线程来实现。 - Joe
显示剩余4条评论
2个回答

2
如果你只有COM组件,并且该组件是STA组件,那么你无法使该组件服务同时调用。但是,这并不妨碍您实例化多个组件实例并对多个实例进行调用。为此,您可能需要考虑使用COM+对象池来获取组件的实例。

请注意,仅因为COM组件正在与之通信的服务能够处理并发请求,这与客户端处理对的并发调用的能力完全无关。

假设您可以获得代码,则无法确定在MTA或自由线程中运行所需的内容;我们不知道实现细节或存储的状态(甚至不知道API的样子)。

如果组件所做的全部工作都是发送请求并处理响应而不存储状态,则应该相当容易,只需切换公寓即可。

但是,有两个原因可能会导致组件成为STA:

1)组件具有大量状态,并且STA是确保状态不会被并发调用破坏的一种方式;在这种情况下,使组件成为MTA/自由线程的工作将很困难,因为您必须保护所有内容,即使这样,由于您可能需要进行所有并发检查,您可能无法获得任何好处(尽管您可能会找到一种将代码转换为易于线程安全的.NET代码的方法)。

2)组件是使用VB6或不支持MTA/自由线程组件的语言编写的;在这种情况下,无法更改公寓模型,您必须使用多个实例或进行线程安全的.NET转换。


1
...不会阻止您实例化组件的多个实例...您所说的“组件”是什么意思?您可以看到COM dll有各种类。现在,我的应用程序的核心线程从文件夹中读取所有请求文件,然后将每个请求的执行放在来自池的新线程上。现在,每个线程都创建一个新的lookup类,创建COMrequest对象、创建COMserver对象并提交请求。因此,每个线程已经创建了自己的一组COM对象。 - EagerToLearn
@EagerToLearn:我的意思是类实例。 - casperOne
是的,正如我之前所述,每个线程都创建了自己的COM对象集,但是这些线程提交的请求并不会并行到达。 - EagerToLearn

2
您可能需要创建多个STA线程,每个线程都有自己的消息循环(*),并且每个线程都有自己的单元线程COM对象实例。
您可以通过调用Thread.SetApartmentState来设置线程的单元状态。
(*) 如果您需要从另一个线程进行调用马歇尔,则需要消息循环。

我第一次使用COM,你能用简单的话告诉我吗?这个COM第三方DLL有很多类。现在我从池中为每个请求创建一个单独的线程进行处理。每个线程都会从COM DLL中创建自己的COMLookup、COMRequest和COMServer对象集合,但是从不同线程提交到不同COMServer对象的请求不能同时到达COM服务器应用程序。 - EagerToLearn
同意,您应该能够创建多个STA线程,每个线程可以创建一个该COM组件的单个实例并与其交互。 - tcarvin
你不能使用系统的线程池,因为它们不是STA线程。创建自己的线程并将它们设置为STA。 - tcarvin
创建COM对象的线程必须调用该对象上的方法。没有代码可供查看,我猜这不是这种情况。 - tcarvin
@EagerToLearn,在创建COM对象之前,您是否在每个线程上调用了SetApartmentState,以确保它们是STA线程? - Joe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接