如何确保 C# 中的 BackgroundWorker 线程的清理?

问题描述 投票:0回答:1

我有一个用 C# 构建的 Excel XLL 插件(使用 ExcelDNA),它执行的任务之一(用户单击按钮时)是调用外部 DLL,该 DLL 执行可能需要几分钟才能完成的数据库请求。 为了避免主 GUI 线程在这段时间内锁定,我将对外部 DLL 的调用包装在 BackgroundWorker 内。 一切似乎都按预期工作,除了现在 Excel 无法正确关闭,我认为是因为后台线程永远不会得到整理?

internal class GetStructure
{
    private DataCache cache;
    public GetStructure(DataCache cache)
    {
        this.cache = cache;
    }

    public void RetrieveStructure()
    { // This is the wrapper for the actual function, as it needs to find and load the Bridging Tool DLLs first

        // ... some code to get the inputs and build a string array called request[]

        ForceDLLLoad(); // Make sure the assembly is loaded before calling the Aucotec DLL
        BackgroundWorker bw = new BackgroundWorker();
        bw.WorkerSupportsCancellation = true;
        bw.DoWork += new DoWorkEventHandler(bw_DoWork);
        bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
        bw.RunWorkerAsync(request);
        bw = null;
    }

    private void bw_DoWork(object sender, DoWorkEventArgs e)
    { // This is the worker thread code - unpack the argument and call the method that gets data
        // Do not attempt to access anything on the main thread in this call
        string[] request = (string[])e.Argument;
        string[] outputs = this.RetrieveStructureFromEB(request);
        e.Result = outputs;
    }
    private string[] RetrieveStructureFromEB(string[] request)
    { // This method is buried privately because a caller needs to make sure the DLLs are loaded first!

        SIMExporterEbmlImport.BridgingTool EB = new BridgingTool();

        string[] outputs = EB.GetStructuredData(cache.Workbook.FullName, request);
        EB = null;
        return outputs;
    }

    private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    { // Unpack the results from the worker thread into an Excel sheet
        string[] outputs = (string[])e.Result;
        if (outputs.Length < 1)
        {
            MessageBox.Show("Unable to retrieve structure from EB.", "DataTool - Get EB Structure", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            Common.ExitToExcel();
            return;
        }

        // ... some code that writes the outputs[] array to an Excel sheet

        MessageBox.Show("EB Structure Data has been retrieved and written to the Config sheet.", "DataTool - Get EB Structure", MessageBoxButtons.OK, MessageBoxIcon.Information);
        Common.ExitToExcel();
        BackgroundWorker worker = (BackgroundWorker)sender;
        worker.Dispose(); worker = null;
        GC.Collect()
    }

代码确实按预期完成执行,因为我得到了在 RunWorkerCompleted 事件中触发的 MessageBox 弹出窗口(成功或失败);我将数据写入 Excel 工作表。问题是,当我尝试关闭 Excel 时,它就变成了僵尸。

如果我尝试关闭 Excel(GUI 消失,但任务管理器保持无头 Excel.exe 在后台运行),我留下的“僵尸”Excel.exe 声称正在等待 splwow64.exe,但我有不知道为什么。

最后,我调用的外部 DLL 发出耗时的请求会消耗大量 RAM (>500 MB) - 我强制使用 GC.Collect() 清除该 RAM,以强制尽快释放该 RAM(这确实如此)。但我的 Excel.exe 仍然无法正常关闭。

c# multithreading backgroundworker excel-dna
1个回答
0
投票

Common.ExitToExcel();
有什么作用?

对 Excel COM 对象模型的所有调用都必须在 Excel 主线程上进行。 您确实必须遵循此规则。 (从主线程进行的 COM 对象模型调用可能仍会因各种原因而失败,因为 Excel 有时会“挂起”对象模型。因此,这是顺利进行 COM 调用的必要条件,但不是充分条件。)

您可以通过确认

Thread.CurrentThread.ManagedId == 1
来检查代码是否在Excel主线程上运行。

您的代码可以通过调用 Excel-DNA 辅助方法

ExcelAsyncUtil.QueueAsMacro
从某个工作线程完成“转换”到主线程。您传递给此方法的委托中的代码将始终在主线程的宏上下文中运行,其中对 COM 对象模型(和 C API)的调用可能会起作用,并且 COM 对象不会让您的进程在之后保持活动状态。关闭 Excel。

如果不遵循此规则,您将得到永远没有机会被清理的 COM 对象。那么Excel进程就会变成僵尸等待这些对象被释放。没有可靠的方法来跟踪与其他线程关联的 COM 对象,相反,仅在主线程上使用的 COM 对象不会使 Excel 保持运行。

您没有显示足够的代码来知道这是否是问题所在,但我猜

Common.ExitToExcel()
正在带您到糟糕的地方。

© www.soinside.com 2019 - 2024. All rights reserved.