从单线程进行COM调用会使线程挂起。

我有一个通过自动化加载项执行一些excel自动化的应用程序。 此加载项是多线程的,并且所有线程都设法对excel COM对象进行调用。由于excel有时在进行多个调用时可能会返回“正忙”异常,因此我将所有调用都包装在一个“重试”函数中。但是我觉得这是不够的。 我现在试图在同一线程上对excel对象进行所有调用,以便所有调用都被我“序列化”,从而降低了excel返回“繁忙”异常的风险。 但是,当该线程尝试访问excel对象时,应用程序挂起。我尝试将线程设置为STA或MTA无济于事。 我用于从单个线程启动所有内容的代码如下: \“违规\”部分应该在\“ DoPass \”中,也许我调用委托的方式有些错误。
public static class ExcelConnector
{
    public static Thread _thread;
    private static int ticket;
    public static Dictionary<Delegate, int> actionsToRun = new Dictionary<Delegate, int>();
    public static Dictionary<int, object> results = new Dictionary<int, object>();


    static ExcelConnector()
    {
        LaunchProcess();
    }

    public static int AddMethodToRun(Delegate method)
    {
        lock (actionsToRun)
        {
            ticket++;
            actionsToRun.Add(method, ticket);
        }
        return ticket;
    }

    public static bool GetTicketResult(int ticket, out object result)
    {
        result = null;
        if (!results.ContainsKey(ticket))
            return false;
        else
        {
            result = results[ticket];
            lock (results)
            {
                results.Remove(ticket);
            }

            return true;
        }
    }

    public static void LaunchProcess()
    {
        _thread = new Thread(new ThreadStart(delegate
                                                 {

                                                     while (true)
                                                     {
                                                         DoPass();
                                                     }
                                                 }));
        //    _thread.SetApartmentState(ApartmentState.STA);
        //   _thread.IsBackground = true;

        _thread.Start();
    }

    public static void DoPass()
    {
        try
        {
            Logger.WriteLine(\"DoPass enter\");


            Dictionary<Delegate, int> copy;
            lock (actionsToRun)
            {
                copy = new Dictionary<Delegate, int>(actionsToRun);
            }


            //run
            foreach (var pair in copy)
            {
                object res = pair.Key.Method.Invoke(
                    pair.Key.Target, null);
                lock (results)
                {
                    results[pair.Value] = res;
                }
                lock (actionsToRun)
                {
                    actionsToRun.Remove(pair.Key);
                }

                Thread.Sleep(100);
            }
        }
        catch (Exception e)
        {
            Logger.WriteError(e);
            //mute
        }
    }
}
编辑:错误可以在一个简单的测试中重现(readline只是为了给ExcelConnector线程工作时间):
var excelApp = new Application();
        excelApp = new Application();
        excelApp.Visible = true;
        excelApp.DisplayAlerts = false;

        System.Action act = delegate
                                {
                                    string s = excelApp.Caption;
                                    Console.WriteLine(s);

                                };
        ExcelConnector.AddMethodToRun(act);
        Console.ReadLine();
    
已邀请:
不幸的是,您所做的事情没有意义,这已经完成了。 Office互操作基于进程外COM。与许多COM接口一样,Excel接口在注册表中被标记为单元线程。这是一种不支持线程的昂贵说法。 COM自动处理不支持线程的组件,它自动将在工作线程上进行的调用封送给创建COM对象的线程。应该是STA的线程,就像任何具有用户界面的程序的主线程一样。如有必要,它将自动创建一个STA线程。这种封送处理的副作用是工作线程进行的调用会自动序列化。毕竟,STA线程一次只能调度一个呼叫。 另一个副作用是死锁并不少见。当STA线程保持繁忙并且没有泵送消息循环时,将发生这种情况。封送由依靠消息循环来调度调用的COM管道代码完成。这种情况很容易调试,您可以使用“调试+全部中断”,“调试+ Windows +线程”并检查STA(或主)线程忙于什么。 还要注意,尝试这种线程化可能是您首先获得此互操作异常的原因的90%。试图获取根本上线程不安全的代码,以同时执行多个操作只是效果不佳。您可以通过互锁自己的代码,标记一个使Excel处于“忙”状态的操作来避免Excel中的“正忙”异常,以便您退出其他线程。当然会很痛苦。     
您必须在要使用COM库的每个线程中初始化COM。从CoInitializeEx文档中,“对于使用COM库的每个线程,必须至少调用一次CoInitializeEx,并且通常只能调用一次。” 而不是尝试实现自己的线程,您应该检查.NET \的Task Parallel Library。在使用TPL中的COM对象时,请检查此问题。本质上,您只需要创建Task对象并将其提交给StaTaskScheduler即可执行。调度程序管理线程的创建和处置,引发异常等。     
我不太了解C#,但是我的猜测是,在启动新线程时,您仍然必须以某种方式初始化COM,以使消息框可以在操作完成时向您的线程发送信号。     
通常无需在.NET中初始化COM -除非您已经完成了一些本机操作,例如P / Invoking。使用IDispatch不需要显式初始化COM。 我认为您只是死锁在某个地方。启动调试器,使其挂起,闯入并为您可以锁定的每个对象(
actionsToRun
results
等)键入type2ѭ。 顺便说一句,您确实应该考虑重组应用程序以使其不同时使用Excel -即使您执行某种“序列化”。即使您成功使用它,它也会回来并永远咬住您。官方不建议这样做,我是根据经验发言的。     

要回复问题请先登录注册