it-swarm.com.ru

Как правильно очистить объекты взаимодействия Excel?

Я использую взаимодействие Excel в C # (ApplicationClass) и поместил следующий код в мое предложение finally:

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

Хотя этот вид работает, процесс Excel.exe остается в фоновом режиме даже после закрытия Excel. Это только выпущено, как только мое приложение закрыто вручную.

Что я делаю не так, или есть альтернатива для обеспечения правильного удаления объектов взаимодействия?

696
HAdes

Excel не завершает работу, потому что ваше приложение все еще содержит ссылки на COM-объекты.

Я предполагаю, что вы вызываете хотя бы один член COM-объекта, не назначая его переменной.

Для меня это был объект excelApp.Worksheets, который я использовал напрямую, не назначая его переменной:

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

Я не знал, что внутренне C # создал оболочку для COM-объекта Worksheets, которая не была освобождена моим кодом (потому что я не знал об этом) и была причиной того, что Excel не был выгружен.

Я нашел решение моей проблемы на этой странице , которая также имеет хорошее правило для использования COM-объектов в C #:

Никогда не используйте две точки с COM-объектами.


Таким образом, с этим знанием правильный способ сделать вышеупомянутое:

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

POST ОБНОВЛЕНИЕ MORTEM:

Я хочу, чтобы каждый читатель очень внимательно прочитал этот ответ Ханса Пассанта, поскольку он объясняет ловушку, в которую я и многие другие разработчики попали. Когда я написал этот ответ несколько лет назад, я не знал о влиянии отладчика на сборщик мусора и сделал неверные выводы. Я не изменяю свой ответ ради истории, но, пожалуйста, прочитайте эту ссылку и not идите путем "двух точек": Понимание сборки мусора в .NET и Очистка взаимодействия Excel Объекты с IDisposable

656
VVS

На самом деле вы можете освободить объект приложения Excel аккуратно, но вы должны позаботиться об этом. 

Рекомендация поддерживать именованную ссылку для абсолютно каждого COM-объекта, к которому вы обращаетесь, и затем явно освобождать его через Marshal.FinalReleaseComObject(), верна в теории, но, к сожалению, очень сложна в управлении на практике. Если кто-то когда-либо проскальзывает и использует «две точки», или итерирует ячейки через цикл for each, или любой другой подобный тип команды, то у вас будут COM-объекты без ссылок и вы рискуете зависнуть. В этом случае невозможно найти причину в коде; вам придется просмотреть весь ваш код на глаз и, надеюсь, найти причину, задачу, которая может быть почти невозможной для большого проекта.

Хорошая новость заключается в том, что вам не нужно поддерживать ссылку на именованную переменную для каждого используемого вами COM-объекта. Вместо этого вызовите GC.Collect(), а затем GC.WaitForPendingFinalizers(), чтобы освободить все (обычно второстепенные) объекты, на которые у вас нет ссылки, а затем явно освободите объекты, на которые у вас есть ссылка на именованную переменную. 

Вы также должны выпустить свои именованные ссылки в обратном порядке важности: сначала выберите объекты диапазона, затем рабочие листы, рабочие книги и, наконец, ваш объект приложения Excel.

Например, если у вас есть переменная объекта Range с именем xlRng, переменная рабочего листа с именем 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);

В большинстве примеров кода, которые вы увидите для очистки COM-объектов из .NET, вызовы GC.Collect() и GC.WaitForPendingFinalizers() делаются ДВАЖДЫ, как в:

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

Это не должно требоваться, однако, если вы не используете Visual Studio Tools for Office (VSTO), который использует финализаторы, которые заставляют весь граф объектов продвигаться в очереди финализации. Такие объекты не будут освобождены до сборки мусора next. Однако, если вы не используете VSTO, вы сможете вызывать GC.Collect() и GC.WaitForPendingFinalizers() только один раз.

Я знаю, что явный вызов GC.Collect() - это нет-нет (и, конечно, дважды это звучит очень болезненно), но, честно говоря, нет никакого способа обойти это. С помощью обычных операций вы будете создавать скрытые объекты, на которые у вас нет ссылок, которые вы, следовательно, не можете освободить никакими другими способами, кроме вызова GC.Collect().

Это сложная тема, но это все, что нужно сделать. После того, как вы установите этот шаблон для своей процедуры очистки, вы можете кодировать как обычно, без использования упаковщиков и т.д. :-)

У меня есть учебник по этому вопросу здесь:

Автоматизация офисных программ с VB.Net/COM Interop

Он написан для VB.NET, но не стоит откладывать на это, принципы точно такие же, как при использовании C #.

268
Mike Rosenblum

Предисловие: мой ответ содержит два решения, поэтому будьте внимательны при чтении и ничего не пропустите.

Существуют различные способы и рекомендации по загрузке экземпляра Excel, такие как: 

  • Освобождение КАЖДОГО com-объекта в явном виде С помощью Marshal.FinalReleaseComObject () (Не забывая о неявно Созданных com-объектах). Чтобы освободить Каждый созданный com-объект, вы можете использовать Правило двух точек, упомянутое здесь:
    Как правильно очистить объекты взаимодействия Excel?

  • Вызов GC.Collect () и GC.WaitForPendingFinalizers (), чтобы заставить CLR освобождать неиспользуемые com-объекты * (На самом деле, это работает, подробности см. В моем втором решении)

  • Проверка, если com-server-application , Возможно, показывает окно сообщения, ожидающее Пользователя, чтобы ответить (хотя я не Уверен, что это может препятствовать закрытию Excel .__, но я слышал об этом несколько раз)

  • Отправка сообщения WM_CLOSE в главное окно Excel

  • Выполнение функции, которая работает С Excel в отдельном AppDomain . Некоторые люди считают, что экземпляр Excel Будет закрыт, когда AppDomain выгружен

  • Уничтожение всех экземпляров Excel, которые были созданы после запуска нашего кода Excel.

НО! Иногда все эти варианты просто не помогают или не могут быть подходящими!

Например, вчера я обнаружил, что в одной из моих функций (которая работает с Excel) Excel продолжает работать после завершения функции. Я перепробовал все! Я тщательно проверил всю функцию 10 раз и добавил Marshal.FinalReleaseComObject () для всего! У меня также были GC.Collect () и GC.WaitForPendingFinalizers (). Я проверил наличие скрытых окон сообщений. Я попытался отправить сообщение WM_CLOSE в главное окно Excel. Я выполнил свою функцию в отдельном домене приложений и выгрузил этот домен. Ничего не помогло! Вариант с закрытием всех экземпляров Excel неуместен, потому что если пользователь запускает другой экземпляр Excel вручную, во время выполнения моей функции, которая также работает с Excel, этот экземпляр также будет закрыт моей функцией. Бьюсь об заклад, пользователь не будет счастлив! Так что, если честно, это неудачный вариант (без обид, ребята). Поэтому я потратил пару часов, прежде чем нашел хорошее (по моему скромному мнению) решение: убить процесс Excel с помощью hWnd его главного окна (это первое решение).

Вот простой код:

[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

/// <summary> Tries to find and kill process by hWnd to the main window of the process.</summary>
/// <param name="hWnd">Handle to the main window of the process.</param>
/// <returns>True if process was found and killed. False if process was not found by hWnd or if it could not be killed.</returns>
public static bool TryKillProcessByMainWindowHwnd(int hWnd)
{
    uint processID;
    GetWindowThreadProcessId((IntPtr)hWnd, out processID);
    if(processID == 0) return false;
    try
    {
        Process.GetProcessById((int)processID).Kill();
    }
    catch (ArgumentException)
    {
        return false;
    }
    catch (Win32Exception)
    {
        return false;
    }
    catch (NotSupportedException)
    {
        return false;
    }
    catch (InvalidOperationException)
    {
        return false;
    }
    return true;
}

/// <summary> Finds and kills process by hWnd to the main window of the process.</summary>
/// <param name="hWnd">Handle to the main window of the process.</param>
/// <exception cref="ArgumentException">
/// Thrown when process is not found by the hWnd parameter (the process is not running). 
/// The identifier of the process might be expired.
/// </exception>
/// <exception cref="Win32Exception">See Process.Kill() exceptions documentation.</exception>
/// <exception cref="NotSupportedException">See Process.Kill() exceptions documentation.</exception>
/// <exception cref="InvalidOperationException">See Process.Kill() exceptions documentation.</exception>
public static void KillProcessByMainWindowHwnd(int hWnd)
{
    uint processID;
    GetWindowThreadProcessId((IntPtr)hWnd, out processID);
    if (processID == 0)
        throw new ArgumentException("Process has not been found by the given main window handle.", "hWnd");
    Process.GetProcessById((int)processID).Kill();
}

Как вы можете видеть, я предоставил два метода в соответствии с шаблоном Try-Parse (я думаю, что здесь уместно): один метод не выдает исключение, если процесс не может быть уничтожен (например, процесс больше не существует) и другой метод генерирует исключение, если процесс не был уничтожен. Единственное слабое место в этом коде - это разрешения безопасности. Теоретически, у пользователя могут не быть прав доступа для уничтожения процесса, но в 99,99% случаев пользователь имеет такие права. Я также проверил это с гостевой учетной записью - она ​​отлично работает.

Итак, ваш код, работающий с Excel, может выглядеть так:

int hWnd = xl.Application.Hwnd;
// ...
// here we try to close Excel as usual, with xl.Quit(),
// Marshal.FinalReleaseComObject(xl) and so on
// ...
TryKillProcessByMainWindowHwnd(hWnd);

Вуаля! Excel прекращен! :)

Хорошо, давайте вернемся ко второму решению, как я и обещал в начале поста .Второе решение - вызвать GC.Collect () и GC.WaitForPendingFinalizers (). Да, они действительно работают, но вы должны быть осторожны здесь!
Многие люди говорят (и я сказал), что вызов GC.Collect () не помогает. Но причина, по которой это не помогло бы, заключается в том, что есть еще ссылки на COM-объекты! Одна из наиболее популярных причин, по которой GC.Collect () не помогает, - это запуск проекта в режиме отладки. В режиме отладки объекты, на которые больше нет ссылок, не будут собирать мусор до конца метода.
Итак, если вы пробовали GC.Collect () и GC.WaitForPendingFinalizers () и это не помогло, попробуйте сделать следующее: 

1) Попробуйте запустить свой проект в режиме выпуска и проверьте, правильно ли закрылся Excel 

2) Обернуть метод работы с Excel в отдельный метод . Итак, вместо чего-то вроде этого:

void GenerateWorkbook(...)
{
  ApplicationClass xl;
  Workbook xlWB;
  try
  {
    xl = ...
    xlWB = xl.Workbooks.Add(...);
    ...
  }
  finally
  {
    ...
    Marshal.ReleaseComObject(xlWB)
    ...
    GC.Collect();
    GC.WaitForPendingFinalizers();
  }
}

ты пишешь:

void GenerateWorkbook(...)
{
  try
  {
    GenerateWorkbookInternal(...);
  }
  finally
  {
    GC.Collect();
    GC.WaitForPendingFinalizers();
  }
}

private void GenerateWorkbookInternal(...)
{
  ApplicationClass xl;
  Workbook xlWB;
  try
  {
    xl = ...
    xlWB = xl.Workbooks.Add(...);
    ...
  }
  finally
  {
    ...
    Marshal.ReleaseComObject(xlWB)
    ...
  }
}

Теперь Excel закроется =)

202
nightcoder

UPDATE: добавлен код C # и ссылка на задания Windows

Я потратил некоторое время, пытаясь выяснить эту проблему, и в то время XtremeVBTalk был самым активным и отзывчивым. Вот ссылка на мой оригинальный пост, Чистое завершение процесса Excel Interop, даже если ваше приложение вылетает . Ниже приведено резюме поста и код, скопированный в этот пост. 

  • Закрытие процесса взаимодействия с помощью Application.Quit() и Process.Kill() по большей части работает, но завершается неудачей, если приложения катастрофически аварийно завершают работу. То есть в случае сбоя приложения процесс Excel все равно будет запущен.
  • Решение состоит в том, чтобы позволить ОС обрабатывать ваши процессы через объекты заданий Windows с помощью вызовов Win32. Когда ваше основное приложение умирает, связанные процессы (например, Excel) также завершаются. 

Я нашел, что это чистое решение, потому что ОС выполняет реальную работу по очистке. Все, что вам нужно сделать, это зарегистрировать процесс Excel.

Код задания Windows

Оборачивает вызовы 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);
    }

}

Примечание о коде конструктора

  • В конструкторе вызывается info.LimitFlags = 0x2000;. 0x2000 - это значение перечисления JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, и это значение определяется как MSDN как:

Вызывает завершение всех процессов, связанных с заданием, когда последняя ручка для работы закрыта.

Дополнительный Win32 API Вызов для получения идентификатора процесса (PID)

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

Используя код

    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);
46
joshgo

Это сработало для проекта, над которым я работал:

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

Мы узнали, что важно установить every ссылку на COM-объект Excel в null, когда вы закончили с ним. Это включало Клетки, Листы и все.

35
Philip Fourie

Все, что находится в пространстве имен Excel, должно быть освобождено. Период

Вы не можете делать:

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

Вы должны делать

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

с последующим выпуском объектов.

29
MagicKat

Я нашел полезный универсальный шаблон, который может помочь реализовать правильный шаблон удаления для COM-объектов, которые требуют вызова Marshal.ReleaseComObject, когда они выходят из области видимости:

Использование:

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();
    } 
}

Шаблон:

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;
        }
    }
}

Ссылка:

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

18
Edward Wilde

Первое - вам никогда не нужно вызывать Marshal.ReleaseComObject(...) или Marshal.FinalReleaseComObject(...) при взаимодействии с Excel. Это запутанный анти-паттерн, но любая информация об этом, в том числе от Microsoft, которая указывает, что вы должны вручную выпускать ссылки COM из .NET, неверна. Дело в том, что среда выполнения .NET и сборщик мусора правильно отслеживают и очищают ссылки COM. Для вашего кода это означает, что вы можете удалить весь цикл while (...) сверху.

Во-вторых, если вы хотите, чтобы ссылки COM на COM-объект вне процесса были очищены после завершения процесса (чтобы процесс Excel закрылся), вам необходимо убедиться, что сборщик мусора работает. Вы делаете это правильно с помощью вызовов GC.Collect() и GC.WaitForPendingFinalizers(). Вызов этого дважды безопасен и гарантирует, что циклы также будут очищены (хотя я не уверен, что это необходимо, и был бы признателен за пример, который показывает это).

В-третьих, при работе под отладчиком локальные ссылки будут искусственно поддерживаться до конца метода (чтобы проверка локальных переменных работала). Поэтому вызовы GC.Collect() не эффективны для очистки объекта, такого как rng.Cells, из того же метода. Вы должны разделить код, выполняющий COM-взаимодействие из очистки GC, на отдельные методы. (Это было ключевым открытием для меня, из одной части ответа, размещенной здесь @nightcoder.)

Таким образом, общая схема будет такой:

Sub WrapperThatCleansUp()

    ' NOTE: Don't call Excel objects in here... 
    '       Debugger would keep alive until end, preventing GC cleanup

    ' Call a separate function that talks to Excel
    DoTheWork()

    ' Now let the GC clean up (twice, to clean up cycles too)
    GC.Collect()    
    GC.WaitForPendingFinalizers()
    GC.Collect()    
    GC.WaitForPendingFinalizers()

End Sub

Sub DoTheWork()
    Dim app As New Microsoft.Office.Interop.Excel.Application
    Dim book As Microsoft.Office.Interop.Excel.Workbook = app.Workbooks.Add()
    Dim worksheet As Microsoft.Office.Interop.Excel.Worksheet = book.Worksheets("Sheet1")
    app.Visible = True
    For i As Integer = 1 To 10
        worksheet.Cells.Range("A" & i).Value = "Hello"
    Next
    book.Save()
    book.Close()
    app.Quit()

    ' NOTE: No calls the Marshal.ReleaseComObject() are ever needed
End Sub

Существует много ложной информации и путаницы по этому вопросу, в том числе много сообщений в MSDN и переполнении стека (и особенно этот вопрос!).

В конечном итоге я убедился, что мне стоит присмотреться и найти правильный совет, - сообщение в блоге Marshal.ReleaseComObject Считается опасным вместе с поиском проблемы со ссылками, оставшимися в живых при отладчике, который сбивал меня с толку в моем предыдущем тестировании.

16
Govert

Я не могу поверить, что эта проблема преследует мир в течение 5 лет .... Если вы создали приложение, вам необходимо сначала закрыть его, прежде чем удалить ссылку.

objExcel = new Excel.Application();  
objBook = (Excel.Workbook)(objExcel.Workbooks.Add(Type.Missing)); 

при закрытии

objBook.Close(true, Type.Missing, Type.Missing); 
objExcel.Application.Quit();
objExcel.Quit(); 

Когда вы создаете приложение Excel, оно открывает программу Excel в фоновом режиме. Вам нужно дать команду программе Excel выйти из системы, прежде чем выпускать ссылку, потому что эта программа Excel не является частью вашего прямого контроля. Поэтому он останется открытым, если ссылка будет выпущена!

Хорошее программирование всем ~~

15
Colin

Обычные разработчики, ни одно из ваших решений не помогло мне, Поэтому я решил реализовать новый трюк .

Сначала позвольте уточнить "Какова наша цель?" => «Не видеть объект Excel после нашей работы в диспетчере задач» 

Хорошо. Пусть нет, чтобы бросить вызов и начать уничтожать его, но не пытайтесь уничтожить другой экземпляр Excel, который работает параллельно.

Итак, получите список текущих процессоров и получите PID процессов Excel, а затем, как только ваша работа будет выполнена, у нас появится новый гость в списке процессов с уникальным PID, найдем и уничтожим только этот.

<помните, что любой новый процесс Excel во время вашей работы Excel будет обнаружен как новый и уничтоженный> <Лучшее решение - захватить PID нового созданного объекта Excel и просто уничтожить его>

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();

Это решает мою проблему, надеюсь, ваша тоже.

12
Mohsen Afshin

Это наверняка кажется слишком сложным. Исходя из моего опыта, для правильного закрытия Excel есть только три ключевых момента:

1: убедитесь, что нет никаких оставшихся ссылок на приложение Excel, которое вы создали (у вас должна быть только одна; установите null)

2: вызов GC.Collect()

3: Excel должен быть закрыт либо пользователем, вручную закрывающим программу, либо вызовом Quit для объекта Excel. (Обратите внимание, что Quit будет функционировать так же, как если бы пользователь пытался закрыть программу, и отобразит диалоговое окно подтверждения, если есть несохраненные изменения, даже если Excel не отображается. Пользователь может нажать «Отмена», а затем Excel не будет закрыт. .)

1 должно произойти раньше, чем 2, но 3 может произойти в любое время.

Одним из способов реализации этого является обертывание объекта Excel взаимодействия с вашим собственным классом, создание экземпляра взаимодействия в конструкторе и реализация IDisposable с Dispose, похожего на

if (!mDisposed) {
   mExcel = null;
   GC.Collect();
   mDisposed = true;
}

Это очистит Excel от проблем вашей программы. После закрытия Excel (вручную пользователем или вызовом Quit) процесс исчезнет. Если программа уже была закрыта, то процесс исчезнет при вызове GC.Collect().

(Я не уверен, насколько это важно, но вам может потребоваться вызов GC.WaitForPendingFinalizers() после вызова GC.Collect(), но избавляться от процесса Excel не обязательно).

Это работало для меня без проблем в течение многих лет. Имейте в виду, что хотя это работает, вам действительно нужно изящно закрыться, чтобы это работало. Вы по-прежнему будете накапливать процессы Excel.exe, если прервете свою программу до очистки Excel (обычно нажимая «stop» во время отладки вашей программы).

10
Dave Cousineau

Чтобы добавить причины, по которым Excel не закрывается, даже когда вы создаете прямые ссылки на каждый объект при чтении, создается цикл For.

For Each objWorkBook As WorkBook in objWorkBooks 'local ref, created from ExcelApp.WorkBooks to avoid the double-dot
   objWorkBook.Close 'or whatever
   FinalReleaseComObject(objWorkBook)
   objWorkBook = Nothing
Next 

'The above does not work, and this is the workaround:

For intCounter As Integer = 1 To mobjExcel_WorkBooks.Count
   Dim objTempWorkBook As Workbook = mobjExcel_WorkBooks.Item(intCounter)
   objTempWorkBook.Saved = True
   objTempWorkBook.Close(False, Type.Missing, Type.Missing)
   FinalReleaseComObject(objTempWorkBook)
   objTempWorkBook = Nothing
Next
9
Grimfort

После попытки

  1. Освободить COM-объекты в обратном порядке
  2. Добавьте GC.Collect() и GC.WaitForPendingFinalizers() дважды в конце
  3. Не более двух точек
  4. Закрыть книгу и выйти из приложения
  5. Запустить в режиме релиза

окончательное решение, которое работает для меня, это переместить один набор

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

что мы добавили в конец функции в оболочку, как показано ниже:

private void FunctionWrapper(string sourcePath, string targetPath)
{
    try
    {
        FunctionThatCallsExcel(sourcePath, targetPath);
    }
    finally
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}
9
D.G.

Принятый ответ здесь является правильным, но также обратите внимание, что нужно избегать не только двухточечных ссылок, но и объектов, которые извлекаются через индекс. Вам также не нужно ждать, пока вы закончите с программой для очистки этих объектов, лучше всего создавать функции, которые будут очищать их, как только вы закончите с ними, когда это возможно. Вот функция, которую я создал, которая назначает некоторые свойства объекта Style с именем xlStyleHeader:

public Excel.Style xlStyleHeader = null;

private void CreateHeaderStyle()
{
    Excel.Styles xlStyles = null;
    Excel.Font xlFont = null;
    Excel.Interior xlInterior = null;
    Excel.Borders xlBorders = null;
    Excel.Border xlBorderBottom = null;

    try
    {
        xlStyles = xlWorkbook.Styles;
        xlStyleHeader = xlStyles.Add("Header", Type.Missing);

        // Text Format
        xlStyleHeader.NumberFormat = "@";

        // Bold
        xlFont = xlStyleHeader.Font;
        xlFont.Bold = true;

        // Light Gray Cell Color
        xlInterior = xlStyleHeader.Interior;
        xlInterior.Color = 12632256;

        // Medium Bottom border
        xlBorders = xlStyleHeader.Borders;
        xlBorderBottom = xlBorders[Excel.XlBordersIndex.xlEdgeBottom];
        xlBorderBottom.Weight = Excel.XlBorderWeight.xlMedium;
    }
    catch (Exception ex)
    {
        throw ex;
    }
    finally
    {
        Release(xlBorderBottom);
        Release(xlBorders);
        Release(xlInterior);
        Release(xlFont);
        Release(xlStyles);
    }
}

private void Release(object obj)
{
    // Errors are ignored per Microsoft's suggestion for this type of function:
    // http://support.Microsoft.com/default.aspx/kb/317109
    try
    {
        System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
    }
    catch { } 
}

Обратите внимание, что мне пришлось установить xlBorders[Excel.XlBordersIndex.xlEdgeBottom] для переменной, чтобы очистить это (не из-за двух точек, которые относятся к перечислению, которое не нужно освобождать, а потому, что объект, на который я ссылаюсь, на самом деле является Border объект, который действительно должен быть освобожден).

Подобные вещи на самом деле не нужны в стандартных приложениях, которые отлично справляются с уборкой, но в приложениях ASP.NET, если вы пропустите хотя бы одно из них, независимо от того, как часто вы вызываете сборщик мусора, Excel будет все еще работает на вашем сервере. 

Это требует большого внимания к деталям и большого количества тестовых выполнений при мониторинге диспетчера задач при написании этого кода, но это избавляет вас от необходимости отчаянного поиска по страницам кода, чтобы найти тот экземпляр, который вы пропустили. Это особенно важно при работе в циклах, где вам нужно освобождать КАЖДУЮ ИНСТАНЦИЮ объекта, даже если он использует одно и то же имя переменной при каждом цикле.

9
Chris McGrath

Я традиционно следовал совету, найденному в ответ ВВС . Тем не менее, пытаясь поддерживать этот ответ в курсе последних опций, я думаю, что все мои будущие проекты будут использовать библиотеку «NetOffice».

NetOffice является полной заменой Office PIA и полностью не зависит от версии. Это коллекция управляемых COM-оболочек, которые могут выполнять очистку, которая часто вызывает такие головные боли при работе с Microsoft Office в .NET.

Некоторые ключевые особенности:

  • В основном не зависит от версии (а функции, зависящие от версии, задокументированы)
  • Нет зависимостей
  • Нет ПИА
  • Нет регистрации
  • Нет ВСТО

Я никоим образом не связан с проектом; Я просто искренне ценю резкое снижение головных болей.

8
BTownTKD

Я точно следовал этому ... Но я все еще сталкивался с проблемами 1 из 1000 раз. Кто знает почему. Время вытащить молоток ...

Сразу после создания экземпляра класса приложения Excel я получаю только что созданный процесс Excel.

Excel = new Microsoft.Office.Interop.Excel.Application();
var process = Process.GetProcessesByName("Excel").OrderByDescending(p => p.StartTime).First();

Затем, когда я выполнил все вышеперечисленные действия по очистке COM, я убедился, что процесс не запущен. Если он все еще работает, убейте его!

if (!process.HasExited)
   process.Kill();
7
craigtadlock

„° º¤ø„ ¸ Снимайте Excel proc и жуйте жевательную резинку ¸ „ø¤º ° ¨

public class MyExcelInteropClass
{
    Excel.Application xlApp;
    Excel.Workbook xlBook;

    public void dothingswithExcel() 
    {
        try { /* Do stuff manipulating cells sheets and workbooks ... */ }
        catch {}
        finally {KillExcelProcess(xlApp);}
    }

    static void KillExcelProcess(Excel.Application xlApp)
    {
        if (xlApp != null)
        {
            int excelProcessId = 0;
            GetWindowThreadProcessId(xlApp.Hwnd, out excelProcessId);
            Process p = Process.GetProcessById(excelProcessId);
            p.Kill();
            xlApp = null;
        }
    }

    [DllImport("user32.dll")]
    static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);
}
7
Antoine Meltzheim

«Никогда не используйте две точки с объектами COM» - это большое правило, позволяющее избежать утечки ссылок на COM, но Excel PIA может привести к утечке в большей степени, чем кажется на первый взгляд.

Одним из таких способов является подписка на любое событие, представляемое любым из объектов COM объектной модели Excel.

Например, подписка на событие WorkbookOpen класса Application.

Немного теории о событиях COM

Классы COM предоставляют группу событий через интерфейсы обратного вызова. Чтобы подписаться на события, клиентский код может просто зарегистрировать объект, реализующий интерфейс обратного вызова, и класс COM будет вызывать его методы в ответ на определенные события. Поскольку интерфейс обратного вызова является COM-интерфейсом, обязанностью реализующего объекта является уменьшение числа ссылок любого COM-объекта, который он получает (в качестве параметра) для любого из обработчиков событий.

Как Excel PIA представляет события COM

Excel PIA представляет события COM класса приложения Excel как обычные события .NET. Всякий раз, когда клиентский код подписывается на событие a .NET (с акцентом на 'a'), PIA создает экземпляр класса a, реализующий интерфейс обратного вызова, и регистрирует его в Excel.

Следовательно, ряд объектов обратного вызова регистрируется в Excel в ответ на различные запросы на подписку из кода .NET. Один объект обратного вызова для каждой подписки на событие.

Интерфейс обратного вызова для обработки событий означает, что PIA должен подписаться на все события интерфейса для каждого запроса подписки на событие .NET. Он не может выбирать. При получении обратного вызова события объект обратного вызова проверяет, заинтересован ли связанный обработчик события .NET в текущем событии или нет, а затем либо вызывает обработчик, либо молча игнорирует обратный вызов.

Влияние на количество ссылок на экземпляры COM

Все эти объекты обратного вызова не уменьшают счетчик ссылок ни одного из COM-объектов, которые они получают (в качестве параметров) для любого из методов обратного вызова (даже для тех, которые игнорируются без предупреждения). Они полагаются исключительно на CLR сборщик мусора для освобождения COM-объектов.

Поскольку запуск GC является недетерминированным, это может привести к задержке процесса Excel на более длительный срок, чем требуется, и создать впечатление «утечки памяти».

Решение

Единственное решение на данный момент состоит в том, чтобы избежать поставщика событий PIA для класса COM и написать свой собственный поставщик событий, который детерминистически высвобождает объекты COM.

Для класса Application это можно сделать, реализовав интерфейс AppEvents, а затем зарегистрировав реализацию в Excel, используя интерфейс IConnectionPointContainer . Класс Application (и в этом отношении все COM-объекты, отображающие события с использованием механизма обратного вызова) реализует интерфейс IConnectionPointContainer.

6
Amit Mittal

Вы должны знать, что Excel очень чувствителен к культуре, в которой вы работаете.

Вы можете обнаружить, что вам нужно установить язык EN-US перед вызовом функций Excel .... Это относится не ко всем функциям, но к некоторым из них.

    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;

Это относится даже если вы используете VSTO.

Для получения дополнительной информации: http://support.Microsoft.com/default.aspx?scid=kb;en-us;Q320369

6
Christian Mogensen

Как уже отмечали другие, вам нужно создать явную ссылку для каждого используемого вами объекта Excel и вызвать Marshal.ReleaseComObject для этой ссылки, как описано в этой статье базы знаний . Вам также нужно использовать try/finally, чтобы гарантировать, что ReleaseComObject всегда вызывается, даже когда выдается исключение. То есть вместо:

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

вам нужно сделать что-то вроде:

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);
}

Вам также нужно вызвать Application.Quit перед освобождением объекта Application, если вы хотите закрыть Excel.

Как вы можете видеть, это быстро становится чрезвычайно громоздким, как только вы пытаетесь сделать что-то даже умеренно сложное. Я успешно разработал .NET-приложения с помощью простого класса-обертки, который включает в себя несколько простых манипуляций с объектной моделью Excel (открытие рабочей книги, запись в Range, сохранение/закрытие рабочей книги и т.д.). Класс-оболочка реализует IDisposable, тщательно реализует Marshal.ReleaseComObject для каждого объекта, который он использует, и не публикует какие-либо объекты Excel для остальной части приложения.

Но этот подход не подходит для более сложных требований. 

Это большой недостаток .NET COM Interop. Для более сложных сценариев я бы серьезно подумал о написании ActiveX DLL на VB6 или другом неуправляемом языке, которому вы можете делегировать все взаимодействия с внешними COM-объектами, такими как Office. Затем вы можете ссылаться на этот ActiveX DLL из своего приложения .NET, и все будет намного проще, поскольку вам нужно будет только выпустить эту одну ссылку.

5
Joe

Когда все вышеперечисленное не работает, попробуйте дать Excel немного времени, чтобы закрыть его листы:

app.workbooks.Close();
Thread.Sleep(500); // adjust, for me it works at around 300+
app.Quit();

...
FinalReleaseComObject(app);
4
spiderman

Убедитесь, что вы освободили все объекты, связанные с Excel!

Я провел несколько часов, пытаясь несколькими способами. Все это отличные идеи, но я наконец нашел свою ошибку: Если вы не отпустите все объекты, ни один из вышеперечисленных способов не поможет вам, как в моем случае. Убедитесь, что вы отпустите все объекты, включая дальний!

Excel.Range rng = (Excel.Range)worksheet.Cells[1, 1];
worksheet.Paste(rng, false);
releaseObject(rng);

Варианты вместе здесь .

3
Ned

Вы должны быть очень осторожны, используя приложения взаимодействия Word/Excel. После того, как мы попробовали все решения, у нас все еще оставалось много процессов "WinWord", которые оставались открытыми на сервере (более 2000 пользователей).

Проработав несколько часов над проблемой, я понял, что если я открою более двух документов одновременно, используя Word.ApplicationClass.Document.Open() в разных потоках, рабочий процесс IIS (w3wp.exe) завершится с ошибкой, и все процессы WinWord будут открыты!

Так что я думаю, что нет абсолютного решения этой проблемы, кроме перехода на другие методы, такие как Office Open XML development.

2
Arvand

Отличная статья об освобождении COM-объектов - это 2.5 Выпуск COM-объектов (MSDN).

Метод, который я бы рекомендовал, состоит в том, чтобы обнулить ссылки на Excel.Interop, если они не являются локальными переменными, а затем дважды вызвать GC.Collect() и GC.WaitForPendingFinalizers(). Переменные Interop с локальной областью видимости будут обрабатываться автоматически.

Это устраняет необходимость сохранять именованную ссылку для каждого COM-объекта.

Вот пример, взятый из статьи:

public class Test {

    // These instance variables must be nulled or Excel will not quit
    private Excel.Application xl;
    private Excel.Workbook book;

    public void DoSomething()
    {
        xl = new Excel.Application();
        xl.Visible = true;
        book = xl.Workbooks.Add(Type.Missing);

        // These variables are locally scoped, so we need not worry about them.
        // Notice I don't care about using two dots.
        Excel.Range rng = book.Worksheets[1].UsedRange;
    }

    public void CleanUp()
    {
        book = null;
        xl.Quit();
        xl = null;

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

Эти слова прямо из статьи:

Практически во всех ситуациях обнуление ссылки на RCW и принудительная сборка мусора приведут к корректной очистке. Если вы также вызовете GC.WaitForPendingFinalizers, сборка мусора будет настолько детерминированной, насколько вы сможете это сделать. То есть вы будете точно уверены, когда именно объект был очищен - по возвращении из второго вызова WaitForPendingFinalizers. В качестве альтернативы вы можете использовать Marshal.ReleaseComObject. Однако учтите, что вам вряд ли когда-нибудь понадобится использовать этот метод.

2
Porkbutts

Правило двух точек не сработало для меня. В моем случае я создал метод для очистки моих ресурсов следующим образом:

private static void Clean()
{
    workBook.Close();
    Marshall.ReleaseComObject(workBook);
    Excel.Quit();
    CG.Collect();
    CG.WaitForPendingFinalizers();
}
2
Hahnemann

В настоящее время я работаю над автоматизацией Office и наткнулся на решение для этого, которое работает каждый раз для меня. Это просто и не предполагает убийства каких-либо процессов.

Кажется, что простым циклом текущих активных процессов и любым способом «доступа» к открытому процессу Excel любой случайный зависший экземпляр Excel будет удален. Приведенный ниже код просто проверяет наличие процессов с именем «Excel», а затем записывает свойство MainWindowTitle процесса в строку. Такое «взаимодействие» с процессом, похоже, заставляет Windows догнать и прервать замороженный экземпляр Excel. 

Я запускаю описанный ниже метод непосредственно перед тем, как надстройка, которую я разрабатываю, завершает работу, поскольку она запускает событие выгрузки. Он удаляет все зависшие экземпляры Excel каждый раз. Честно говоря, я не совсем уверен, почему это работает, но он работает хорошо для меня и может быть помещен в конец любого приложения Excel, не беспокоясь ни о двойных точках, Marshal.ReleaseComObject, ни о процессах уничтожения. Я был бы очень заинтересован в любых предложениях относительно того, почему это эффективно.

public static void SweepExcelProcesses()
{           
            if (Process.GetProcessesByName("Excel").Length != 0)
            {
                Process[] processes = Process.GetProcesses();
                foreach (Process process in processes)
                {
                    if (process.ProcessName.ToString() == "Excel")
                    {                           
                        string title = process.MainWindowTitle;
                    }
                }
            }
}
1
Tom Brearley

Я думаю, что отчасти это то, как фреймворк обрабатывает приложения Office, но я могу ошибаться. В некоторые дни некоторые приложения очищают процессы немедленно, а в другие дни кажется, что они ждут, пока приложение не закроется. В общем, я перестал обращать внимание на детали и просто убедиться, что в конце дня не будет никаких дополнительных процессов.

Кроме того, и, возможно, я слишком упрощаю вещи, но я думаю, что вы можете просто ...

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();

Как я уже говорил ранее, я не склонен обращать внимание на детали того, когда процесс Excel появляется или исчезает, но это обычно работает для меня. Я также не люблю держать процессы Excel в течение какого-то другого времени, кроме минимального количества времени, но я, вероятно, просто параноик по этому поводу.

1
bill_the_loser

«Похоже, это было слишком сложно. Исходя из моего опыта, для правильного закрытия Excel есть только три ключевых момента:

1: убедитесь, что нет никаких оставшихся ссылок на приложение Excel, которое вы создали (у вас должна быть только одна; установите его в null)

2: вызвать GC.Collect ()

3: Excel должен быть закрыт либо пользователем, вручную закрывающим программу, либо вызовом Quit для объекта Excel. (Обратите внимание, что Quit будет работать так же, как если бы пользователь пытался закрыть программу, и отобразит диалоговое окно подтверждения, если есть несохраненные изменения, даже если Excel не виден. Пользователь может нажать «Отмена», после чего Excel не будет закрыт. .)

1 должно произойти раньше, чем 2, но 3 может произойти в любое время.

Одним из способов реализации этого является обертывание объекта Excel взаимодействия с вашим собственным классом, создание экземпляра взаимодействия в конструкторе и реализация IDisposable с Dispose, похожего на

Это очистит Excel от проблем вашей программы. После закрытия Excel (вручную пользователем или вызовом Quit) процесс исчезнет. Если программа уже была закрыта, то процесс исчезнет при вызове GC.Collect ().

(Я не уверен, насколько это важно, но вы можете захотеть вызвать GC.WaitForPendingFinalizers () после вызова GC.Collect (), но это не является строго необходимым, чтобы избавиться от процесса Excel.)

Это работало для меня без проблем в течение многих лет. Имейте в виду, что хотя это работает, вам действительно нужно изящно закрыться, чтобы это работало. Вы по-прежнему будете накапливать процессы Excel.exe, если прервете свою программу до очистки Excel (обычно нажимая «stop» во время отладки вашей программы). '

1
Бурда Евгений

Принятый ответ не работал для меня. Следующий код в деструкторе сделал свою работу.

if (xlApp != null)
{
    xlApp.Workbooks.Close();
    xlApp.Quit();
}

System.Diagnostics.Process[] processArray = System.Diagnostics.Process.GetProcessesByName("Excel");
foreach (System.Diagnostics.Process process in processArray)
{
    if (process.MainWindowTitle.Length == 0) { process.Kill(); }
}
1
Martin

Использование:

[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

Объявите это, добавьте код в блок finally:

finally
{
    GC.Collect();
    GC.WaitForPendingFinalizers();
    if (excelApp != null)
    {
        excelApp.Quit();
        int hWnd = excelApp.Application.Hwnd;
        uint processID;
        GetWindowThreadProcessId((IntPtr)hWnd, out processID);
        Process[] procs = Process.GetProcessesByName("Excel");
        foreach (Process p in procs)
        {
            if (p.Id == processID)
                p.Kill();
        }
        Marshal.FinalReleaseComObject(excelApp);
    }
}
1
Shivam Srivastava

Мое решение

[DllImport("user32.dll")]
static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);

private void GenerateExcel()
{
    var Excel = new Microsoft.Office.Interop.Excel.Application();
    int id;
    // Find the Excel Process Id (ath the end, you kill him
    GetWindowThreadProcessId(Excel.Hwnd, out id);
    Process excelProcess = Process.GetProcessById(id);

try
{
    // Your code
}
finally
{
    Excel.Quit();

    // Kill him !
    excelProcess.Kill();
}
1
Loart

Как некоторые, возможно, уже написали, не только важно, как вы закроете Excel (объект); Также важно, как вы открываете это, а также по типу проекта.

В приложении WPF в основном один и тот же код работает без или с очень небольшим количеством проблем.

У меня есть проект, в котором один и тот же файл Excel обрабатывается несколько раз для другого значения параметра - например, Разбор его на основе значений в общем списке. 

Я поместил все связанные с Excel функции в базовый класс, а анализатор - в подкласс (разные анализаторы используют общие функции Excel). Я не хотел, чтобы Excel открывался и снова закрывался для каждого элемента в общем списке, поэтому я открыл его только один раз в базовом классе и закрыл в подклассе. У меня были проблемы при переносе кода в настольное приложение. Я перепробовал многие из вышеупомянутых решений. Функция GC.Collect() уже была реализована ранее, дважды как предложено.

Тогда я решил, что переместу код для открытия Excel в подкласс. Вместо того, чтобы открывать только один раз, теперь я создаю новый объект (базовый класс), открываю Excel для каждого элемента и закрываю его в конце. Существует некоторое снижение производительности, но на основании нескольких тестов процессы Excel закрываются без проблем (в режиме отладки), поэтому также удаляются временные файлы. Я продолжу тестирование и напишу еще, если получу обновления.

Суть в следующем: вы также должны проверить код инициализации, особенно если у вас много классов и т.д.

1
Blaz Brencic

Там у меня есть идея, попробуйте убить процесс Excel, который вы открыли:

  1. перед открытием заявки получите все идентификаторы процессов с именем oldProcessIds.
  2. откройте заявку.
  3. получить сейчас все идентификаторы процесса исключения, названные nowProcessIds.
  4. когда нужно выйти, убить идентификаторы исключений между oldProcessIds и nowProcessIds.

    private static Excel.Application GetExcelApp()
         {
            if (_excelApp == null)
            {
                var processIds = System.Diagnostics.Process.GetProcessesByName("Excel").Select(a => a.Id).ToList();
                _excelApp = new Excel.Application();
                _excelApp.DisplayAlerts = false;
    
                _excelApp.Visible = false;
                _excelApp.ScreenUpdating = false;
                var newProcessIds = System.Diagnostics.Process.GetProcessesByName("Excel").Select(a => a.Id).ToList();
                _excelApplicationProcessId = newProcessIds.Except(processIds).FirstOrDefault();
            }
    
            return _excelApp;
        }
    
    public static void Dispose()
        {
            try
            {
                _excelApp.Workbooks.Close();
                _excelApp.Quit();
                System.Runtime.InteropServices.Marshal.ReleaseComObject(_excelApp);
                _excelApp = null;
                GC.Collect();
                GC.WaitForPendingFinalizers();
                if (_excelApplicationProcessId != default(int))
                {
                    var process = System.Diagnostics.Process.GetProcessById(_excelApplicationProcessId);
                    process?.Kill();
                    _excelApplicationProcessId = default(int);
                }
            }
            catch (Exception ex)
            {
                _excelApp = null;
            }
    
        }
    
0
Aloha

Просто чтобы добавить другое решение ко многим из перечисленных здесь, используя автоматизацию C++/ATL (я полагаю, вы могли бы использовать нечто подобное из VB/C # ??)

Excel::_ApplicationPtr pXL = ...
  :
SendMessage ( ( HWND ) m_pXL->GetHwnd ( ), WM_DESTROY, 0, 0 ) ;

Это работает как очарование для меня ...

0
dicksters

Вот действительно простой способ сделать это:

[DllImport("User32.dll")]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
...

int objExcelProcessId = 0;

Excel.Application objExcel = new Excel.Application();

GetWindowThreadProcessId(new IntPtr(objExcel.Hwnd), out objExcelProcessId);

Process.GetProcessById(objExcelProcessId).Kill();
0
tjarrett

Протестировано с Microsoft Excel 2016

Действительно проверенное решение.

Ссылка на C #, пожалуйста, смотрите: https://stackoverflow.com/a/1307180/10442623

Ссылка на VB.net доступна по адресу: https://stackoverflow.com/a/54044646/10442623

1 включить работу класса

2 реализовать класс для обработки апропиата утилизации процесса Excel

0
SamSar

До сих пор кажется, что все ответы включают некоторые из них:

  1. Убить процесс
  2. Используйте GC.Collect ()
  3. Следите за каждым COM-объектом и выпускайте его правильно.

Что заставляет меня оценить, насколько сложна эта проблема :)

Я работал над библиотекой, чтобы упростить доступ к Excel, и я стараюсь сделать так, чтобы люди, использующие ее, не оставили беспорядок (скрестив пальцы).

Вместо того, чтобы писать непосредственно на интерфейсах, которые предоставляет Interop, я делаю методы расширения, чтобы облегчить жизнь. Как ApplicationHelpers.CreateExcel () или workbook.CreateWorksheet ("mySheetNameThatWillBeValidated"). Естественно, все, что создано, может привести к проблеме позже при очистке, поэтому я на самом деле предпочитаю убить процесс как последнее средство. Тем не менее, очистка должным образом (третий вариант), вероятно, наименее разрушительна и наиболее контролируема.

Итак, в этом контексте мне было интересно, не будет ли лучше сделать что-то вроде этого:

public abstract class ReleaseContainer<T>
{
    private readonly Action<T> actionOnT;

    protected ReleaseContainer(T releasible, Action<T> actionOnT)
    {
        this.actionOnT = actionOnT;
        this.Releasible = releasible;
    }

    ~ReleaseContainer()
    {
        Release();
    }

    public T Releasible { get; private set; }

    private void Release()
    {
        actionOnT(Releasible);
        Releasible = default(T);
    }
}

Я использовал «Releasible», чтобы избежать путаницы с Disposable. Расширить это до IDisposable должно быть легко.

Реализация как это:

public class ApplicationContainer : ReleaseContainer<Application>
{
    public ApplicationContainer()
        : base(new Application(), ActionOnExcel)
    {
    }

    private static void ActionOnExcel(Application application)
    {
        application.Show(); // extension method. want to make sure the app is visible.
        application.Quit();
        Marshal.FinalReleaseComObject(application);
    }
}

И можно сделать что-то подобное для всех видов COM-объектов.

В заводском методе:

    public static Application CreateExcelApplication(bool hidden = false)
    {
        var Excel = new ApplicationContainer().Releasible;
        Excel.Visible = !hidden;

        return Excel;
    }

Я ожидал бы, что каждый контейнер будет должным образом разрушен GC, и поэтому автоматически вызовет Quit и Marshal.FinalReleaseComObject.

Комментарии? Или это ответ на вопрос третьего рода?

0
akirakonenu