c# - 从单线程进行 COM 调用会挂起线程

标签 c# multithreading excel com interop

我有一个应用程序可以通过自动化插件执行一些 excel 自动化。 此加载项是多线程的,所有线程都设法调用 excel COM 对象。因为在进行多次调用时 excel 有时会返回“忙”异常,所以我将所有调用都包装在“重试”函数中。但是我觉得这是inneficient。 我现在试图在同一个线程上对 excel 对象进行所有调用,以便所有调用都由我“序列化”,从而降低 excel 返回“忙”异常的风险。 但是,当此线程尝试访问 excel 对象时,应用程序会挂起。我已尝试将线程设置为 STA 或 MTA,但无济于事。

我用来从单个线程启动所有内容的代码如下: “违规”部分应该在“DoPass”中,也许我调用委托(delegate)的方式有些错误。

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 接口(interface)一样,Excel 接口(interface)在注册表中被标记为单元线程。这是说他们不支持线程的一种昂贵方式。

COM 自动处理不支持线程的组件,它自动将工作线程上的调用编码到创建 COM 对象的线程。这应该是一个 STA 线程,就像任何具有用户界面的程序的主线程一样。如有必要,它将自动创建一个 STA 线程。这种编码(marshal)处理的一个副作用是工作线程发出的调用会自动序列化。毕竟,STA 线程一次只能分派(dispatch)一个调用。

另一个副作用是死锁并不少见。当 STA 线程保持忙碌并且不发送消息循环时会发生这种情况。编码由依赖于消息循环来分派(dispatch)调用的 COM 管道代码完成。这种情况非常容易调试,您可以使用 Debug + Break All、Debug + Windows + Threads 并检查 STA(或 Main)线程忙于什么。

另请注意,尝试这种线程可能是您首先遇到此互操作异常的 90% 原因。试图获得从根本上线程不安全的代码来同时做不止一件事是行不通的。您可以通过联锁您的自己的 代码来避免 Excel 出现“忙”异常,标记一个将 Excel 置于“忙”状态的操作,这样您就可以退出其他线程。这样做当然很痛苦。

关于c# - 从单线程进行 COM 调用会挂起线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5647977/

相关文章:

android - 使用线程休眠更新 UI

Java线程-我想按顺序生成数字,例如: 1, 2,3,4...等等(只有2个线程)

vba - 另一个公式中使用的单元格旁边的单元格的平均值

Python读取SAS生成的XML类型.xls文件

c# - 如何将 C# 二维数组返回给 VBScript

javascript - 以编程方式登录网站并提交表单

c# - WPF:BindingExpression 产生的值对目标无效

c# - 在 WPF 应用程序中按下 Return 时如何模拟 Tab 键按下?

C# - System.Transactions.TransactionScope

ios - MKMapView - 在后台线程中添加注释