C# 如何正确清理 Excel 互操作对象?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/158706/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 16:08:01  来源:igfitidea点击:

How do I properly clean up Excel interop objects?

提问by HAdes

I'm using the Excel interop in C# (ApplicationClass) and have placed the following code in my finally clause:

我在 C# ( ApplicationClass) 中使用 Excel 互操作,并在我的 finally 子句中放置了以下代码:

while (System.Runtime.InteropServices.Marshal.ReleaseComObject(excelSheet) != 0) { }
excelSheet = null;
GC.Collect();
GC.WaitForPendingFinalizers();

Although this kind of works, the Excel.exeprocess is still in the background even after I close Excel. It is only released once my application is manually closed.

尽管这种工作有效,但Excel.exe即使在我关闭 Excel 后,该过程仍处于后台。只有在我的应用程序被手动关闭后才会发布。

What am I doing wrong, or is there an alternative to ensure interop objects are properly disposed of?

我做错了什么,或者有没有其他方法可以确保正确处理互操作对象?

采纳答案by VVS

Excel does not quit because your application is still holding references to COM objects.

Excel 不会退出,因为您的应用程序仍然持有对 COM 对象的引用。

I guess you're invoking at least one member of a COM object without assigning it to a variable.

我猜您是在调用 COM 对象的至少一个成员,而没有将其分配给变量。

For me it was the excelApp.Worksheetsobject which I directly used without assigning it to a variable:

对我来说,它是我直接使用的excelApp.Worksheets对象,而没有将其分配给变量:

Worksheet sheet = excelApp.Worksheets.Open(...);
...
Marshal.ReleaseComObject(sheet);

I didn't know that internally C# created a wrapper for the WorksheetsCOM object which didn't get released by my code (because I wasn't aware of it) and was the cause why Excel was not unloaded.

我不知道 C# 在内部为WorksheetsCOM 对象创建了一个包装器,它没有被我的代码释放(因为我不知道它),这也是 Excel 没有卸载的原因。

I found the solution to my problem on this page, which also has a nice rule for the usage of COM objects in C#:

我在这个页面上找到了我的问题的解决方案,它对于在 C# 中使用 COM 对象也有一个很好的规则:

Never use two dots with COM objects.

切勿对 COM 对象使用两个点。



So with this knowledge the right way of doing the above is:

因此,有了这些知识,执行上述操作的正确方法是:

Worksheets sheets = excelApp.Worksheets; // <-- The important part
Worksheet sheet = sheets.Open(...);
...
Marshal.ReleaseComObject(sheets);
Marshal.ReleaseComObject(sheet);

POST MORTEM UPDATE:

验尸更新:

I want every reader to read this answer by Hans Passant very carefully as it explains the trap I and lots of other developers stumbled into. When I wrote this answer years ago I didn't know about the effect the debugger has to the garbage collector and drew the wrong conclusions. I keep my answer unaltered for the sake of history but please read this link and don'tgo the way of "the two dots": Understanding garbage collection in .NETand Clean up Excel Interop Objects with IDisposable

我希望每位读者都非常仔细地阅读 Hans Passant 的这个回答,因为它解释了我和许多其他开发人员偶然发现的陷阱。几年前我写这个答案时,我不知道调试器对垃圾收集器的影响,并得出了错误的结论。为了历史起见,我保持我的答案不变,但请阅读此链接,不要走“两点”的道路:了解 .NET 中的垃圾收集使用 IDisposable 清理 Excel 互操作对象

回答by Philip Fourie

This worked for a project I was working on:

这适用于我正在从事的项目:

excelApp.Quit();
Marshal.ReleaseComObject (excelWB);
Marshal.ReleaseComObject (excelApp);
excelApp = null;

We learned that it was important to set everyreference to an Excel COM object to null when you were done with it. This included Cells, Sheets, and everything.

我们了解到,在完成对 Excel COM 对象的每个引用后,将其设置为 null非常重要。这包括单元格、表格和所有内容。

回答by MagicKat

Anything that is in the Excel namespace needs to be released. Period

Excel 命名空间中的任何内容都需要释放。时期

You can't be doing:

你不能这样做:

Worksheet ws = excel.WorkBooks[1].WorkSheets[1];

You have to be doing

你必须做

Workbooks books = excel.WorkBooks;
Workbook book = books[1];
Sheets sheets = book.WorkSheets;
Worksheet ws = sheets[1];

followed by the releasing of the objects.

然后释放对象。

回答by bill_the_loser

I think that some of that is just the way that the framework handles Office applications, but I could be wrong. On some days, some applications clean up the processes immediately, and other days it seems to wait until the application closes. In general, I quit paying attention to the details and just make sure that there aren't any extra processes floating around at the end of the day.

我认为其中一些只是框架处理 Office 应用程序的方式,但我可能是错的。在某些日子里,一些应用程序会立即清理进程,而在其他日子里,它似乎要等到应用程序关闭。总的来说,我不再关注细节,只是确保在一天结束时没有任何额外的流程。

Also, and maybe I'm over simplifying things, but I think you can just...

另外,也许我把事情简化了,但我认为你可以......

objExcel = new Excel.Application();
objBook = (Excel.Workbook)(objExcel.Workbooks.Add(Type.Missing));
DoSomeStuff(objBook);
SaveTheBook(objBook);
objBook.Close(false, Type.Missing, Type.Missing);
objExcel.Quit();

Like I said earlier, I don't tend to pay attention to the details of when the Excel process appears or disappears, but that usually works for me. I also don't like to keep Excel processes around for anything other than the minimal amount of time, but I'm probably just being paranoid on that.

就像我之前说的,我不倾向于关注 Excel 进程何时出现或消失的细节,但这通常对我有用。除了最少的时间之外,我也不喜欢将 Excel 进程保留在任何地方,但我可能只是对此感到偏执。

回答by Joe

As others have pointed out, you need to create an explicit reference for every Excel object you use, and call Marshal.ReleaseComObject on that reference, as described in this KB article. You also need to use try/finally to ensure ReleaseComObject is always called, even when an exception is thrown. I.e. instead of:

正如其他人指出的那样,您需要为您使用的每个 Excel 对象创建一个显式引用,并在该引用上调用 Marshal.ReleaseComObject,如本知识库文章 中所述。您还需要使用 try/finally 来确保始终调用 ReleaseComObject,即使在抛出异常时也是如此。即而不是:

Worksheet sheet = excelApp.Worksheets(1)
... do something with sheet

you need to do something like:

您需要执行以下操作:

Worksheets sheets = null;
Worksheet sheet = null
try
{ 
    sheets = excelApp.Worksheets;
    sheet = sheets(1);
    ...
}
finally
{
    if (sheets != null) Marshal.ReleaseComObject(sheets);
    if (sheet != null) Marshal.ReleaseComObject(sheet);
}

You also need to call Application.Quit before releasing the Application object if you want Excel to close.

如果您希望 Excel 关闭,您还需要在释放 Application 对象之前调用 Application.Quit。

As you can see, this quickly becomes extremely unwieldy as soon as you try to do anything even moderately complex. I have successfully developed .NET applications with a simple wrapper class that wraps a few simple manipulations of the Excel object model (open a workbook, write to a Range, save/close the workbook etc). The wrapper class implements IDisposable, carefully implements Marshal.ReleaseComObject on every object it uses, and does not pubicly expose any Excel objects to the rest of the app.

如您所见,一旦您尝试做任何复杂的事情,这很快就会变得非常笨拙。我已经使用一个简单的包装类成功开发了 .NET 应用程序,该类包装了 Excel 对象模型的一些简单操作(打开工作簿、写入范围、保存/关闭工作簿等)。包装类实现 IDisposable,在它使用的每个对象上仔细实现 Marshal.ReleaseComObject,并且不会向应用程序的其余部分公开公开任何 Excel 对象。

But this approach doesn't scale well for more complex requirements.

但是这种方法不能很好地适应更复杂的需求。

This is a big deficiency of .NET COM Interop. For more complex scenarios, I would seriously consider writing an ActiveX DLL in VB6 or other unmanaged language to which you can delegate all interaction with out-proc COM objects such as Office. You can then reference this ActiveX DLL from your .NET application, and things will be much easier as you will only need to release this one reference.

这是.NET COM Interop 的一大缺陷。对于更复杂的场景,我会认真考虑用 VB6 或其他非托管语言编写 ActiveX DLL,您可以将所有与外部 COM 对象(如 Office)的交互委托给它。然后您可以从您的 .NET 应用程序中引用这个 ActiveX DLL,事情会容易得多,因为您只需要释放这个引用。

回答by Mike Rosenblum

You can actually release your Excel Application object cleanly, but you do have to take care.

您实际上可以干净地释放您的 Excel Application 对象,但您必须小心。

The advice to maintain a named reference for absolutely every COM object you access and then explicitly release it via Marshal.FinalReleaseComObject()is correct in theory, but, unfortunately, very difficult to manage in practice. If one ever slips anywhere and uses "two dots", or iterates cells via a for eachloop, or any other similar kind of command, then you'll have unreferenced COM objects and risk a hang. In this case, there would be no way to find the cause in the code; you would have to review all your code by eye and hopefully find the cause, a task that could be nearly impossible for a large project.

为您访问的绝对每个 COM 对象维护一个命名引用,然后通过它显式释放它的建议Marshal.FinalReleaseComObject()在理论上是正确的,但不幸的是,在实践中很难管理。如果有人滑到任何地方并使用“两个点”,或者通过for each循环或任何其他类似的命令迭代单元格,那么您将拥有未引用的 COM 对象并有挂起的风险。在这种情况下,将无法在代码中找到原因;您必须亲眼检查所有代码并希望找到原因,这对于大型项目来说几乎是不可能的。

The good news is that you do not actually have to maintain a named variable reference to every COM object you use. Instead, call GC.Collect()and then GC.WaitForPendingFinalizers()to release all the (usually minor) objects to which you do not hold a reference, and then explicitly release the objects to which you do hold a named variable reference.

好消息是您实际上不必维护对您使用的每个 COM 对象的命名变量引用。相反,调用GC.Collect()然后GC.WaitForPendingFinalizers()释放所有您没有持有引用的(通常是次要的)对象,然后显式释放您持有命名变量引用的对象。

You should also release your named references in reverse order of importance: range objects first, then worksheets, workbooks, and then finally your Excel Application object.

您还应该按重要性的相反顺序释放命名引用:首先是范围对象,然后是工作表、工作簿,最后是您的 Excel 应用程序对象。

For example, assuming that you had a Range object variable named xlRng, a Worksheet variable named xlSheet, a Workbook variable named xlBookand an Excel Application variable named xlApp, then your cleanup code could look something like the following:

例如,假设您有一个名为 的 Range 对象变量xlRng、一个名为的 Worksheet 变量xlSheet、一个名为的 Workbook 变量xlBook和一个名为的 Excel 应用程序变量xlApp,那么您的清理代码可能如下所示:

// Cleanup
GC.Collect();
GC.WaitForPendingFinalizers();

Marshal.FinalReleaseComObject(xlRng);
Marshal.FinalReleaseComObject(xlSheet);

xlBook.Close(Type.Missing, Type.Missing, Type.Missing);
Marshal.FinalReleaseComObject(xlBook);

xlApp.Quit();
Marshal.FinalReleaseComObject(xlApp);

In most code examples you'll see for cleaning up COM objects from .NET, the GC.Collect()and GC.WaitForPendingFinalizers()calls are made TWICE as in:

在大多数用于从 .NET 清除 COM 对象的代码示例中,GC.Collect()GC.WaitForPendingFinalizers()调用进行了两次,如下所示:

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();

This should not be required, however, unless you are using Visual Studio Tools for Office (VSTO), which uses finalizers that cause an entire graph of objects to be promoted in the finalization queue. Such objects would not be released until the nextgarbage collection. However, if you are not using VSTO, you should be able to call GC.Collect()and GC.WaitForPendingFinalizers()just once.

但是,这应该不是必需的,除非您使用的是 Visual Studio Tools for Office (VSTO),它使用终结器导致在终结队列中提升整个对象图。这样的对象直到下一次垃圾收集才会被释放。但是,如果您没有使用 VSTO,您应该能够调用GC.Collect()并且GC.WaitForPendingFinalizers()只调用一次。

I know that explicitly calling GC.Collect()is a no-no (and certainly doing it twice sounds very painful), but there is no way around it, to be honest. Through normal operations you will generate hidden objects to which you hold no reference that you, therefore, cannot release through any other means other than calling GC.Collect().

我知道明确调用GC.Collect()是禁忌(当然,这样做两次听起来很痛苦),但说实话,没有办法绕过它。通过正常操作,您将生成没有引用的隐藏对象,因此除了调用GC.Collect().

This is a complex topic, but this really is all there is to it. Once you establish this template for your cleanup procedure you can code normally, without the need for wrappers, etc. :-)

这是一个复杂的话题,但这就是它的全部内容。一旦你为你的清理过程建立了这个模板,你就可以正常编码,而无需包装器等:-)

I have a tutorial on this here:

我在这里有一个教程:

Automating Office Programs with VB.Net / COM Interop

使用 VB.Net / COM Interop 自动化办公程序

It's written for VB.NET, but don't be put off by that, the principles are exactly the same as when using C#.

它是为 VB.NET 编写的,但不要被它推迟,原理与使用 C# 时完全相同。

回答by Mike Rosenblum

You need to be aware that Excel is very sensitive to the culture you are running under as well.

您需要注意 Excel 对您所运行的文化也非常敏感。

You may find that you need to set the culture to EN-US before calling Excel functions. This does not apply to all functions - but some of them.

您可能会发现在调用 Excel 函数之前需要将区域性设置为 EN-US。这并不适用于所有功能 - 但其中一些。

    CultureInfo en_US = new System.Globalization.CultureInfo("en-US"); 
    System.Threading.Thread.CurrentThread.CurrentCulture = en_US;
    string filePathLocal = _applicationObject.ActiveWorkbook.Path;
    System.Threading.Thread.CurrentThread.CurrentCulture = orgCulture;

This applies even if you are using VSTO.

即使您使用的是 VSTO,这也适用。

For details: http://support.microsoft.com/default.aspx?scid=kb;en-us;Q320369

详情:http: //support.microsoft.com/default.aspx?scid=kb;en-us;Q320369

回答by Edward Wilde

I founda useful generic template that can help implement the correct disposal pattern for COM objects, that need Marshal.ReleaseComObject called when they go out of scope:

找到了一个有用的通用模板,它可以帮助为 COM 对象实现正确的处理模式,当它们超出范围时需要调用 Marshal.ReleaseComObject:

Usage:

用法:

using (AutoReleaseComObject<Application> excelApplicationWrapper = new AutoReleaseComObject<Application>(new Application()))
{
    try
    {
        using (AutoReleaseComObject<Workbook> workbookWrapper = new AutoReleaseComObject<Workbook>(excelApplicationWrapper.ComObject.Workbooks.Open(namedRangeBase.FullName, false, false, missing, missing, missing, true, missing, missing, true, missing, missing, missing, missing, missing)))
        {
           // do something with your workbook....
        }
    }
    finally
    {
         excelApplicationWrapper.ComObject.Quit();
    } 
}

Template:

模板:

public class AutoReleaseComObject<T> : IDisposable
{
    private T m_comObject;
    private bool m_armed = true;
    private bool m_disposed = false;

    public AutoReleaseComObject(T comObject)
    {
        Debug.Assert(comObject != null);
        m_comObject = comObject;
    }

#if DEBUG
    ~AutoReleaseComObject()
    {
        // We should have been disposed using Dispose().
        Debug.WriteLine("Finalize being called, should have been disposed");

        if (this.ComObject != null)
        {
            Debug.WriteLine(string.Format("ComObject was not null:{0}, name:{1}.", this.ComObject, this.ComObjectName));
        }

        //Debug.Assert(false);
    }
#endif

    public T ComObject
    {
        get
        {
            Debug.Assert(!m_disposed);
            return m_comObject;
        }
    }

    private string ComObjectName
    {
        get
        {
            if(this.ComObject is Microsoft.Office.Interop.Excel.Workbook)
            {
                return ((Microsoft.Office.Interop.Excel.Workbook)this.ComObject).Name;
            }

            return null;
        }
    }

    public void Disarm()
    {
        Debug.Assert(!m_disposed);
        m_armed = false;
    }

    #region IDisposable Members

    public void Dispose()
    {
        Dispose(true);
#if DEBUG
        GC.SuppressFinalize(this);
#endif
    }

    #endregion

    protected virtual void Dispose(bool disposing)
    {
        if (!m_disposed)
        {
            if (m_armed)
            {
                int refcnt = 0;
                do
                {
                    refcnt = System.Runtime.InteropServices.Marshal.ReleaseComObject(m_comObject);
                } while (refcnt > 0);

                m_comObject = default(T);
            }

            m_disposed = true;
        }
    }
}

Reference:

参考:

http://www.deez.info/sengelha/2005/02/11/useful-idisposable-class-3-autoreleasecomobject/

http://www.deez.info/sengelha/2005/02/11/useful-idisposable-class-3-autoreleasecomobject/

回答by joshgo

UPDATE: Added C# code, and link to Windows Jobs

更新:添加了 C# 代码,并链接到 Windows 作业

I spent sometime trying to figure out this problem, and at the time XtremeVBTalk was the most active and responsive. Here is a link to my original post, Closing an Excel Interop process cleanly, even if your application crashes. Below is a summary of the post, and the code copied to this post.

我花了一些时间试图找出这个问题,当时 XtremeVBTalk 是最活跃和响应最快的。这是我的原始帖子的链接,即使您的应用程序崩溃,也能干净利落地关闭 Excel 互操作进程。以下是该帖子的摘要,并将代码复制到该帖子中。

  • Closing the Interop process with Application.Quit()and Process.Kill()works for the most part, but fails if the applications crashes catastrophically. I.e. if the app crashes, the Excel process will still be running loose.
  • The solution is to let the OS handle the cleanup of your processes through Windows Job Objectsusing Win32 calls. When your main application dies, the associated processes (i.e. Excel) will get terminated as well.
  • 使用Application.Quit()和关闭 Interop 进程Process.Kill()在大多数情况下是有效的,但如果应用程序发生灾难性崩溃,则会失败。即,如果应用程序崩溃,Excel 进程仍将处于松散状态。
  • 解决方案是让操作系统使用 Win32 调用通过Windows 作业对象处理进程的清理工作。当您的主应用程序终止时,相关的进程(即 Excel)也将终止。

I found this to be a clean solution because the OS is doing real work of cleaning up. All you have to do is registerthe Excel process.

我发现这是一个干净的解决方案,因为操作系统正在做真正的清理工作。您所要做的就是注册Excel 进程。

Windows Job Code

Windows 工作代码

Wraps the Win32 API Calls to register Interop processes.

包装 Win32 API 调用以注册互操作进程。

public enum JobObjectInfoType
{
    AssociateCompletionPortInformation = 7,
    BasicLimitInformation = 2,
    BasicUIRestrictions = 4,
    EndOfJobTimeInformation = 6,
    ExtendedLimitInformation = 9,
    SecurityLimitInformation = 5,
    GroupInformation = 11
}

[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
    public int nLength;
    public IntPtr lpSecurityDescriptor;
    public int bInheritHandle;
}

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
    public Int64 PerProcessUserTimeLimit;
    public Int64 PerJobUserTimeLimit;
    public Int16 LimitFlags;
    public UInt32 MinimumWorkingSetSize;
    public UInt32 MaximumWorkingSetSize;
    public Int16 ActiveProcessLimit;
    public Int64 Affinity;
    public Int16 PriorityClass;
    public Int16 SchedulingClass;
}

[StructLayout(LayoutKind.Sequential)]
struct IO_COUNTERS
{
    public UInt64 ReadOperationCount;
    public UInt64 WriteOperationCount;
    public UInt64 OtherOperationCount;
    public UInt64 ReadTransferCount;
    public UInt64 WriteTransferCount;
    public UInt64 OtherTransferCount;
}

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
    public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
    public IO_COUNTERS IoInfo;
    public UInt32 ProcessMemoryLimit;
    public UInt32 JobMemoryLimit;
    public UInt32 PeakProcessMemoryUsed;
    public UInt32 PeakJobMemoryUsed;
}

public class Job : IDisposable
{
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    static extern IntPtr CreateJobObject(object a, string lpName);

    [DllImport("kernel32.dll")]
    static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);

    private IntPtr m_handle;
    private bool m_disposed = false;

    public Job()
    {
        m_handle = CreateJobObject(null, null);

        JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
        info.LimitFlags = 0x2000;

        JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
        extendedInfo.BasicLimitInformation = info;

        int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
        IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
        Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);

        if (!SetInformationJobObject(m_handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length))
            throw new Exception(string.Format("Unable to set information.  Error: {0}", Marshal.GetLastWin32Error()));
    }

    #region IDisposable Members

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    #endregion

    private void Dispose(bool disposing)
    {
        if (m_disposed)
            return;

        if (disposing) {}

        Close();
        m_disposed = true;
    }

    public void Close()
    {
        Win32.CloseHandle(m_handle);
        m_handle = IntPtr.Zero;
    }

    public bool AddProcess(IntPtr handle)
    {
        return AssignProcessToJobObject(m_handle, handle);
    }

}

Note about Constructor code

关于构造函数代码的注意事项

  • In the constructor, the info.LimitFlags = 0x2000;is called. 0x2000is the JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSEenum value, and this value is defined by MSDNas:
  • 在构造函数中,info.LimitFlags = 0x2000;调用了 。0x2000JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE枚举值,该值由MSDN定义为:

Causes all processes associated with the job to terminate when the last handle to the job is closed.

当作业的最后一个句柄关闭时,导致与作业关联的所有进程终止。

Extra Win32 API Call to get the Process ID (PID)

额外的 Win32 API 调用以获取进程 ID (PID)

    [DllImport("user32.dll", SetLastError = true)]
    public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

Using the code

使用代码

    Excel.Application app = new Excel.ApplicationClass();
    Job job = new Job();
    uint pid = 0;
    Win32.GetWindowThreadProcessId(new IntPtr(app.Hwnd), out pid);
    job.AddProcess(Process.GetProcessById((int)pid).Handle);

回答by Mohsen Afshin

Common developers, none of your solutions worked for me, so I decide to implement a new trick.

普通的开发人员,你们的解决方案都不适合我,所以我决定实施一个新技巧

First let specify "What is our goal?" => "Not to see excel object after our job in task manager"

首先让我们明确“我们的目标是什么?” =>“在我们在任务管理器中工作后看不到excel对象”

Ok. Let no to challenge and start destroying it, but consider not to destroy other instance os Excel which are running in parallel.

好的。让 no 挑战并开始销毁它,但考虑不要销毁其他并行运行的 os Excel 实例。

So , get the list of current processors and fetch PID of EXCEL processes , then once your job is done, we have a new guest in processes list with a unique PID ,find and destroy just that one.

因此,获取当前处理器的列表并获取 EXCEL 进程的 PID,然后一旦您的工作完成,我们在进程列表中有一个具有唯一 PID 的新来宾,找到并销毁那个。

< keep in mind any new excel process during your excel job will be detected as new and destroyed > < A better solution is to capture PID of new created excel object and just destroy that>

< 请记住,在您的 excel 作业期间,任何新的 excel 进程都将被检测为新的并已销毁> < 更好的解决方案是捕获新创建的 excel 对象的 PID 并销毁它>

Process[] prs = Process.GetProcesses();
List<int> excelPID = new List<int>();
foreach (Process p in prs)
   if (p.ProcessName == "EXCEL")
       excelPID.Add(p.Id);

.... // your job 

prs = Process.GetProcesses();
foreach (Process p in prs)
   if (p.ProcessName == "EXCEL" && !excelPID.Contains(p.Id))
       p.Kill();

This resolves my issue, hope yours too.

这解决了我的问题,希望你也是。