Pergunta

Eu estou usando a interoperabilidade do Excel em C # (ApplicationClass) e ter colocado o seguinte código no meu cláusula finally:

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

Embora este tipo de obras, o processo Excel.exe ainda está em segundo plano, mesmo depois de eu fechar Excel. Ele só é liberado após a minha aplicação é fechada manualmente.

O que estou fazendo de errado, ou há uma alternativa para assegurar a interoperabilidade objetos são devidamente eliminados?

Foi útil?

Solução

Excel não sair porque a sua aplicação ainda está segurando as referências a objetos COM.

Eu acho que você está invocando pelo menos um membro de um objeto COM sem atribuí-la a uma variável.

Para mim, foi o excelApp.Worksheets objeto que eu usei diretamente sem atribuí-la a uma variável:

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

Eu não sabia que, internamente, C # criado um wrapper para o planilhas objeto COM que não se liberado pelo meu código (porque eu não estava ciente disso) e foi a causa por que Excel não foi descarregado.

Eu encontrei a solução para o meu problema na esta página , que também tem uma regra de bom para o uso de objetos COM em C #:

Nunca use dois pontos com objetos COM.


Assim, com este conhecimento da maneira correta de fazer o acima é:

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

POST MORTEM UPDATE:

Eu quero que cada leitor a ler esta resposta por Hans Passant com muito cuidado, uma vez que explica a armadilha que eu e muitos outros desenvolvedores tropeçou. Quando eu escrevi esta resposta anos atrás eu não sabia sobre o efeito que o depurador tem para o coletor de lixo e tirou as conclusões erradas. Eu mantenho a minha resposta inalterado por causa da história, mas por favor leia este link e não seguir o caminho de "os dois pontos": coleta de lixo Entendimento em .NET e Limpe Excel Interop objetos com IDisposable

Outras dicas

Você pode realmente liberar o objeto Application Excel limpa, mas você tem que tomar cuidado.

O conselho de manter uma referência nomeada para absolutamente todos os objetos COM acessar e, em seguida, explicitamente soltá-lo via Marshal.FinalReleaseComObject() está correto em teoria, mas, infelizmente, muito difícil de gerir na prática. Se ninguém nunca desliza em qualquer lugar e utiliza "dois pontos", ou repete células através de um loop for each, ou qualquer outro tipo similar de comando, então você vai ter objetos COM sem referência e arriscar um jeito. Neste caso, não haveria maneira de encontrar a causa no código; você teria que rever todo o seu código por olho e espero encontrar a causa, uma tarefa que poderia ser quase impossível para um grande projeto.

A boa notícia é que você não tem que realmente manter uma referência variável chamada para cada objeto COM que você usa. Em vez disso, chamada GC.Collect() e depois GC.WaitForPendingFinalizers() para liberar toda a (geralmente menores) opõe-se que você não manter uma referência, e depois explicitamente liberar os objetos aos quais você manter uma referência variável chamada.

Você também deve liberar suas referências nomeadas em ordem inversa de importância:. Gama objetos em primeiro lugar, em seguida, planilhas, pastas de trabalho e, finalmente, o seu objeto Application Excel

Por exemplo, supondo que você tinha uma variável objeto Range chamado xlRng, um xlSheet Planilha variável chamada, um livro variável chamada xlBook e um xlApp Aplicação Excel variável chamada, então o seu código de limpeza poderia ser algo como o seguinte:

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

Na maioria dos exemplos de código você verá pela limpeza COM objetos de .NET, as chamadas GC.Collect() e GC.WaitForPendingFinalizers() são feitas duas vezes como em:

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

Este não deve ser necessário, no entanto, a menos que você estiver usando o Visual Studio Tools for Office (VSTO), que usa finalizadores que causam um gráfico inteiro de objetos a serem promovidos na fila de finalização. Tais objetos não seria libertado até que o próximo coleta de lixo. No entanto, se você não estiver usando VSTO, você deve ser capaz de chamar GC.Collect() e GC.WaitForPendingFinalizers() apenas uma vez.

Eu sei que chamar explicitamente GC.Collect() é um não-não (e certamente fazê-lo sons duas vezes muito dolorosas), mas não há maneira de contornar isso, para ser honesto. Através de operações normais, você irá gerar objetos escondidos para que você mantenha nenhuma referência que, portanto, não pode liberar através de quaisquer outros do que chamar GC.Collect() meios.

Este é um tema complexo, mas isso realmente é tudo que existe para ela. Depois de estabelecer esse modelo para o seu procedimento de limpeza você pode codificar normalmente, sem a necessidade de invólucros, etc.: -)

Eu tenho um tutorial sobre isso aqui:

Automatizando programas do Office com VB.Net / COM Interop

Foi escrito para VB.NET, mas não ser adiadas por isso, os princípios são exatamente a mesma de quando usando C #.

Prefácio: a minha resposta contém duas soluções, por isso tome cuidado ao ler e não perca nada

.

Existem diferentes maneiras e conselhos de como fazer instância descarregar Excel, tais como:

  • Releasing CADA com objeto explicitamente com Marshal.FinalReleaseComObject () (Não esquecendo implicitamente com-objetos criados). para liberar cada objeto com criado, você pode usar a regra de 2 pontos mencionados aqui:
    Como posso corretamente limpar interoperabilidade Excel objetos?

  • Chamando GC.Collect () e GC.WaitForPendingFinalizers () para fazer CLR liberar com-objetos não utilizados * (Na verdade, ele funciona, ver a minha segunda solução para detalhes)

  • Verificando com-server-aplicação talvez mostra uma caixa de mensagem em espera para o usuário a responder (embora eu não sou -se que pode impedir que o Excel fechando, mas eu ouvi sobre isso um pouco vezes)

  • Enviando mensagem WM_CLOSE para a principal janela Excel

  • A execução da função que as obras com o Excel em um AppDomain separado. Algumas pessoas acreditam instância Excel irá ser fechada, quando o domínio de aplicação é descarregado.

  • Matar todas as instâncias do Excel que foram instanciados após nosso código excel-interoping começou.

MAS! Às vezes, todas essas opções só não ajuda ou não pode ser apropriado!

Por exemplo, ontem eu descobri que em uma de minhas funções (que trabalha com excel) Excel continua a funcionar após o término da função. Eu tentei de tudo! I cuidadosamente verificada toda a função 10 vezes e acrescentou Marshal.FinalReleaseComObject () para tudo! Eu também tive GC.Collect () e GC.WaitForPendingFinalizers (). Eu verifiquei para caixas de mensagens ocultas. Tentei enviar mensagem WM_CLOSE para a janela principal do Excel. Executei a minha função em um AppDomain separado e descarregado nesse domínio. Nada ajudou! A opção com o fechamento de todas as instâncias do Excel é impróprio, porque se o usuário iniciar outra instância Excel manualmente, durante a execução do meu função que funciona também com o Excel, então essa instância também serão fechadas pela minha função. Aposto que o usuário não será feliz! Então, honestamente, esta é uma opção lame (sem caras ofensa). Então eu passei um par de horas antes de encontrar um boa solução (na minha humilde opinião) : processo de Kills excel por hWnd de sua janela principal (é a primeira solução).

Aqui está o código simples:

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

Como você pode ver que eu forneci dois métodos, de acordo com Try-Parse padrão (acho que é apropriado aqui): um método não lançar a exceção se o processo não poderia ser morto (por exemplo, o processo não existe mais), e um outro método lança a exceção se o processo não foi morto. O lugar um fraco neste código é permissões de segurança. Teoricamente, o usuário pode não ter permissões para matar o processo, mas em 99,99% dos casos, o usuário tem essas permissões. Eu também testei com uma conta de convidado -. Ele funciona perfeitamente

Assim, seu código, trabalhar com o Excel, pode ficar assim:

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

Voila! Excel é encerrado! :)

Ok, vamos voltar para a segunda solução, como prometi no início do post. A segunda solução é chamar GC.Collect () e GC.WaitForPendingFinalizers (). Sim, eles realmente funcionam, mas você precisa ter cuidado aqui!
Muitas pessoas dizem (e eu disse) que chamar GC.Collect () não ajuda. Mas a razão pela qual não iria ajudar é se ainda existem referências a objetos COM! Uma das razões mais populares para GC.Collect () não sendo útil está executando o projeto em Debug-mode. Em objetos de modo de depuração que não são realmente referenciados mais não vai ser lixo coletado até o final do método.
Então, se você tentou GC.Collect () e GC.WaitForPendingFinalizers () e ele não ajudou, Tente fazer o seguinte:

1) Tente executar seu projeto no modo de versão e verifique se o Excel fechado corretamente

2) Enrole o método de trabalho com Excel em um método separado. Assim, em vez de algo como isto:

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

você escreve:

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

Agora, Excel será fechado =)

Atualizar : Adicionado código C #, e link para o Windows Jobs

Eu passei algum tempo tentando descobrir este problema, e no momento XtremeVBTalk foi o mais ativo e receptivo. Aqui está um link para o meu post original, Fechando um processo de Excel Interop limpa, mesmo que o seu aplicação trava . Abaixo está um resumo do post, e o código copiado para este post.

  • Fechando o processo Interop com Application.Quit() e Process.Kill() funciona para a maior parte, mas não consegue se os aplicativos falhar catastroficamente. Ou seja, se as falhas de aplicativos, o processo Excel ainda estará correndo solta.
  • A solução é deixar a alça OS a limpeza de seus processos através de Windows Job objetos usando chamadas Win32. Quando seus principais morre de aplicação, os processos associados (ou seja, Excel) vai ter terminado bem.

Eu encontrei este para ser uma solução limpa porque o sistema operacional está fazendo o trabalho real da limpeza. Tudo que você tem a fazer é registrar o processo de Excel.

Windows Código Job

Wraps as chamadas de API Win32 para registrar processos Interop.

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

}

Nota sobre o código Construtor

  • No construtor, o info.LimitFlags = 0x2000; é chamado. 0x2000 é o valor JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE enum, e este valor é definido por MSDN como:

Faz com que todos os processos associados ao trabalho para terminar quando o último identificador para o trabalho está fechada.

extra Win32 API chamada para obter o ID do processo (PID)

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

Usando o código

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

Isso funcionou para um projeto que eu estava trabalhando em:

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

Nós aprendemos que era importante conjunto de todas referência a um objeto Excel COM como nulo quando você foi feito com ele. Isto incluiu Células, folhas, e tudo mais.

Tudo o que está no namespace Excel precisa ser liberado. Período

Você não pode estar fazendo:

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

Você tem que estar fazendo

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

seguido pela libertação dos objectos.

Em primeiro lugar - você não tem que Marshal.ReleaseComObject(...) chamada ou Marshal.FinalReleaseComObject(...) ao fazer interoperabilidade Excel. É um anti-padrão confuso, mas nenhuma informação sobre isso, inclusive da Microsoft, que indica que você tem que liberar manualmente referências COM da NET está incorreto. O fato é que o coletor .NET runtime e lixo manter corretamente a par e referências até COM limpas. Para o seu código, isso significa que você pode remover todo `while (...) laço na parte superior.

Em segundo lugar, se você quiser garantir que as referências COM para um objeto COM out-of-process são limpos quando suas extremidades de processo (para que o processo Excel será fechado), você precisa garantir que as pistas de coletor de lixo. Você pode fazer isso corretamente com chamadas para GC.Collect() e GC.WaitForPendingFinalizers(). Chamar isso duas vezes é seguro e garante que os ciclos são definitivamente limpo também (embora eu não tenho certeza que é necessário, e gostaria de receber um exemplo que mostra isso).

Em terceiro lugar, quando executando sob o depurador, referências locais serão artificialmente mantido vivo até o final do método (por isso que as obras de inspeção variável local). Assim chamadas GC.Collect() não são eficazes para a limpeza de objeto como rng.Cells do mesmo método. Você deve dividir o código fazendo a interoperabilidade COM da limpeza GC em métodos separados. (Esta foi uma descoberta chave para mim, de uma parte da resposta postado aqui por @nightcoder.)

O padrão geral seria assim:

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

Há um monte de informações falsas e confusão sobre esta questão, incluindo muitos posts no MSDN e no Stack Overflow (e especialmente esta questão!).

O que finalmente me convenceu a ter um olhar mais atento e descobrir o conselho certo era post Marshal.ReleaseComObject considerado perigoso , juntamente com encontrar o problema com referências mantida viva sob o depurador que estava confundindo meu teste antes.

encontrado um modelo útil genérico que pode ajudar a implementar o padrão de descarte correto para objetos COM, essa necessidade Marshal.ReleaseComObject chamado quando eles saem de escopo:

Uso:

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

Referência:

http: //www.deez .info / sengelha / 2005/02/11 / útil-IDisposable de classe-3-autoreleasecomobject /

Eu não posso acreditar este problema tem assombrado o mundo por 5 anos .... Se você criou um aplicativo, você precisa desligá-lo primeiro antes de remover o link.

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

quando fechar

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

Quando lhe novas uma aplicação excel, ele abre um programa excel em segundo plano. Você precisa de comando que o programa Excel para sair antes de soltar o link, porque esse programa excel não é parte de seu controle direto. Portanto, ele vai ficar aberto se o link é lançado!

Boa programação todos ~~

desenvolvedores comuns, nenhuma das suas soluções trabalhou para mim, então eu decidir implementar um novo truque .

Primeiro vamos especificar "Qual é o nosso objetivo?" => "Não ver excel objeto após o nosso trabalho no gerenciador de tarefas"

Ok. Que nenhum de desafio e começar a destruí-la, mas consideram não destruir outra instância do sistema operacional Excel que estão sendo executados em paralelo.

Assim, começ a lista de processadores atuais e buscar PID de processos Excel, em seguida, uma vez que seu trabalho é feito, nós temos um novo hóspede na lista de processos com um PID único, encontrar e destruir apenas esse.

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

Isso resolve o meu problema, a sua esperança também.

Esta certeza parece que foi sobre-complicada. Da minha experiência, existem apenas três coisas fundamentais para obter o Excel para fechar corretamente:

1: Certifique-se que não estão permanecendo referências ao aplicativo Excel que você criou (você deve ter apenas um de qualquer maneira; set-lo para null)

2: chamada GC.Collect()

3: Excel tem de ser fechado, seja pelo usuário fechar o programa manualmente, ou por você chamando Quit no objeto Excel. (Note-se que Quit funcionará como se o usuário tentou fechar o programa, e irá apresentar uma janela de confirmação se houver alterações não salvas, mesmo se o Excel não é visível. O usuário pode pressionar cancelar e, em seguida, Excel não terá sido fechada .)

1 precisa acontecer antes de 2, mas 3 pode acontecer a qualquer momento.

Uma maneira de implementar isso é para embrulhar o objeto de interoperabilidade Excel com sua própria classe, criar a instância de interoperabilidade no construtor, e implementar IDisposable com Descarte procurando algo como

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

Isso irá limpar excel do lado de seu programa de coisas. Uma vez que Excel está fechado (manualmente pelo usuário ou por você chamando Quit) o processo vai embora. Se o programa já foi fechado, então o processo vai desaparecer na chamada GC.Collect().

(não tenho certeza quanto é importante, mas você pode querer uma chamada GC.WaitForPendingFinalizers() após a chamada GC.Collect() mas não é estritamente necessário para se livrar do processo de Excel.)

Isso tem funcionado para mim sem problema há anos. Tenha em mente que, enquanto isso funciona, você realmente tem que fechar graciosamente para que ele funcione. Você ainda vai ter acúmulo de processos Excel.exe se você interromper o seu programa antes de Excel é limpo (geralmente por bater "stop" enquanto o programa está sendo depurado).

Para adicionar razões por que Excel não fecha, mesmo quando você cria refrences diretos a cada objeto em cima de leitura, criação, é o 'para' loop.

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

Eu tradicionalmente seguido o conselho encontrado em da VVS resposta . No entanto, em um esforço para manter esta resposta up-to-date com as últimas opções, eu acho que todos os meus projetos futuros usar a biblioteca "NetOffice".

NetOffice é um substituto completo para PIAs do Office e é completamente versão agnóstica. É uma coleção de wrappers COM gerenciados que podem lidar com a limpeza que muitas vezes faz com que tais dores de cabeça quando se trabalha com o Microsoft Office em .NET.

Algumas das principais características são:

  • Na maior parte versão independente (características e dependente da versão são documentados)
  • No dependências
  • No PIA
  • Nenhum registo
  • No VSTO

Eu estou em nenhuma maneira afiliado com o projeto; Eu só realmente apreciar a redução gritante em dores de cabeça.

A resposta aceito aqui é correto, mas também tomar nota que não só referências "dois pontos" precisam ser evitadas, mas também objetos que são recuperados através do índice. Você também não precisa esperar até terminar com o programa para limpar esses objetos, é melhor para criar funções que irá limpar-los assim que você terminar com eles, quando possível. Aqui é uma função que eu criei que atribui algumas propriedades de um estilo de objeto chamado 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 { } 
}

Observe que eu tinha que definir xlBorders[Excel.XlBordersIndex.xlEdgeBottom] a uma variável, a fim de limpar isso (não por causa dos dois pontos, que se referem a uma enumeração que não precisa ser liberado, mas porque o objeto que eu estou me referindo é na verdade um objeto Border que não precisa ser liberado).

Este tipo de coisa não é realmente necessário em aplicações padrão, que fazem um grande trabalho de limpeza após si, mas em aplicações ASP.NET, se você perder sequer um desses, não importa quantas vezes você chamar o coletor de lixo , o Excel ainda estar em execução no servidor.

Ela exige muita atenção aos detalhes e muitas execuções de teste, enquanto monitorando o Gerenciador de Tarefas ao escrever este código, mas isso poupa o trabalho de procurar desesperadamente por páginas de código para encontrar o exemplo que você perdeu. Isto é especialmente importante quando se trabalha em loops, onde você precisa para liberar cada instância de um objeto, mesmo que ele usa o mesmo nome de variável cada vez que ele faz um loop.

Depois de tentar

  1. Release COM objetos na ordem inversa
  2. Adicionar GC.Collect() e GC.WaitForPendingFinalizers() duas vezes no final
  3. Não mais do que dois pontos
  4. Fechar pasta de trabalho e aplicação parar
  5. Executar no modo de versão

a solução final que funciona para mim é mover um conjunto de

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

que adicionado ao final da função a um invólucro, como segue:

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

Eu segui este exatamente ... Mas eu ainda correu para questões de 1 em cada 1000 vezes. Quem sabe o porquê. Hora de trazer para fora o martelo ...

Logo após a classe Application Excel é instanciado I obter um porão do processo de Excel que acabou de ser criado.

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

Em seguida, depois de já ter feito toda a COM acima clean-up, eu certificar-se de que o processo não está sendo executado. Se ele ainda estiver em execução, matá-lo!

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

¨ ° º¤ø “¸ Atire proc Excel e mastigar chiclete ¸“ø¤º ° ¨

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

Você precisa estar ciente de que o Excel é muito sensível à cultura que está sendo executado sob também.

Você pode achar que você precisa para definir a cultura EN-US antes de chamar funções do Excel. Isto não se aplica a todas as funções -., Mas alguns deles

    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;

Isto aplica-se mesmo se você estiver usando o VSTO.

Para mais detalhes: http://support.microsoft.com /default.aspx?scid=kb;en-us;Q320369

"Nunca use dois pontos com objetos COM" é uma grande regra de ouro para o vazamento evitar de referências COM, mas Excel PIA pode levar ao vazamento em formas mais do que evidente à primeira vista.

Uma dessas formas é subscrever qualquer evento exposto por qualquer um dos objetos COM do modelo de objeto do Excel.

Por exemplo, assinando evento WorkbookOpen da classe Application.

Algumas teoria sobre eventos COM

Com aulas expor um grupo de eventos através de interfaces de call-back. A fim de se inscrever em eventos, o código do cliente pode simplesmente registrar um objeto que implementa a interface call-back ea classe COM irá chamar seus métodos em resposta a eventos específicos. Como a interface call-back é uma interface COM, é dever do objeto de execução para diminuir a contagem de referência de qualquer objeto COM que recebe (como um parâmetro) para qualquer um dos manipuladores de eventos.

Como Excel PIA expor COM Eventos

Excel PIA expõe eventos COM da classe de aplicativo Excel como eventos convencionais .NET. Sempre que os subscreve código de cliente para um evento .NET (ênfase em 'a'), PIA cria um instância de uma classe que implementa a interface call-back e registra-lo com o Excel .

Assim, uma série de objetos call-back obter registrado com o Excel em resposta a diferentes pedidos de subscrição a partir do código .NET. objeto call-back por assinatura evento.

Uma interface de retorno de chamada para o evento significa que, PIA tem para se inscrever em todos os eventos de interface para cada pedido .NET subscrição manipulação de eventos. Não pode escolher. Ao receber um evento de retorno de chamada, as verificações de objeto de retorno de chamada se o manipulador de eventos .NET associado está interessado no evento atual ou não e em seguida, chama o manipulador ou silenciosamente ignora o call-back.

Efeito sobre COM contagens de referência exemplo

Todos estes objetos call-back não diminuir a contagem de referência de qualquer um dos objetos COM que recebem (como parâmetros) para qualquer um dos métodos de call-back (mesmo para os que são silenciosamente ignorados). Eles dependem unicamente na CLR coletor de lixo para liberar os objetos COM.

Desde GC prazo é não determinística, isso pode levar à exploração off do processo de Excel para uma duração mais longa do que o desejado e criar uma impressão de uma 'fuga de memória'.

Solução

A única solução a partir de agora é evitar provedor de eventos do PIA para a classe COM e escrever seu próprio provedor de eventos que deterministically lançamentos COM objetos.

Para a classe de aplicações, isso pode ser feito através da implementação das AppEvents interface e, em seguida, registrar a implementação com o Excel usando IConnectionPointContainer interface de . A classe de aplicativos (e para essa matéria toda COM objetos expondo eventos usando mecanismo de retorno de chamada) implementa a interface IConnectionPointContainer.

Como outros apontaram, você precisa criar uma referência explícita para cada objeto Excel que você usa, e chamar Marshal.ReleaseComObject em que a referência, conforme descrito em este artigo KB . Você também precisa usar try / finally para garantir ReleaseComObject é sempre chamado, mesmo quando uma exceção é lançada. Ou seja, em vez de:

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

que você precisa fazer algo como:

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

Você também precisa chamar Application.Quit antes de liberar o objeto Application se você quiser Excel para perto.

Como você pode ver, este rapidamente se torna extremamente difícil de manejar, logo que você tentar fazer qualquer coisa, mesmo moderadamente complexa. Tenho desenvolvido com sucesso aplicativos .NET com uma classe de invólucro simples que envolve um simples poucas manipulações do modelo de objeto Excel (abre um livro, escrita para um Range, salvar / fechar a pasta de trabalho etc). A classe implementa invólucro IDisposable, implementa cuidadosamente Marshal.ReleaseComObject em cada objeto que ele usa, e não pubicly expor qualquer Excel objetos para o resto do aplicativo.

Mas esta abordagem não escala bem para mais requisitos complexos.

Esta é uma grande deficiência de .NET COM Interop. Para cenários mais complexos, eu iria seriamente considerar escrever um DLL ActiveX no VB6 ou outra língua não gerenciado para que você pode delegar toda a interação com out-proc COM objetos como Office. Você pode fazer referência esta DLL ActiveX a partir da aplicação .NET, e as coisas vão ser muito mais fácil, pois você só vai precisar para liberar esta referência um.

Quando todo o material acima não funcionou, tente dar Excel algum tempo para fechar suas folhas:

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

...
FinalReleaseComObject(app);

Certifique-se de que você liberar todos os objetos relacionados para o Excel!

Eu passei algumas horas, tentando várias maneiras. Todos são grandes idéias, mas eu finalmente encontrei o meu erro: Se você não liberar todos os objetos, nenhuma das formas acima pode ajudá-lo , como no meu caso. Certifique-se de liberar todos os objetos, incluindo uma gama!

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

As opções estão juntos aqui .

A regra dos dois pontos não funcionou para mim. No meu caso eu criei um método para limpar os meus recursos da seguinte forma:

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

Você deve ter muito cuidado ao usar aplicativos Word / Excel interoperabilidade. Depois de tentar todas as soluções que ainda tinha um monte de processo "WinWord" deixado aberto no servidor (com mais de 2000 usuários).

Depois de trabalhar sobre o problema por horas, eu percebi que se eu abrir mais de um par de documentos usando Word.ApplicationClass.Document.Open() em diferentes threads simultaneamente, processo de trabalho (w3wp.exe) IIS iria falhar deixando tudo WinWord processa! Aberta

Então eu acho que não há nenhuma solução absoluta para este problema, mas a mudança para outros métodos, como Office Open desenvolvimento XML .

Um ótimo artigo sobre liberando COM objetos é 2.5 Liberando COM objetos (MSDN).

O método que eu defendo é nulo suas referências Excel.Interop se eles são variáveis ??não-locais, e em seguida, chamar GC.Collect() e GC.WaitForPendingFinalizers() duas vezes. variáveis ??Interop localmente escopo será realizado automaticamente.

Isso elimina a necessidade de manter uma referência nomeado para todas objeto COM.

Aqui está um exemplo retirado do artigo:

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

Estas palavras são retas do artigo:

Em quase todas as situações, anulando a referência RCW e forçando uma coleta de lixo irá limpar corretamente. Se você também chamar GC.WaitForPendingFinalizers, coleta de lixo será tão determinista como você pode fazê-lo. Ou seja, você vai ter certeza exatamente quando o objeto foi limpa-no retorno da segunda chamada para WaitForPendingFinalizers. Como alternativa, você pode usar Marshal.ReleaseComObject. No entanto, nota que você é muito improvável que alguma vez precisar usar esse método.

A minha solução

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

A resposta aceita não funcionou para mim. O código a seguir no processo de destruição fez o trabalho.

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

Eu estou trabalhando atualmente em automação de escritório e ter tropeçado uma solução para este que trabalha o tempo todo para mim. É simples e não envolve matando todos os processos.

Parece que meramente looping através dos processos ativos no momento e, de qualquer maneira 'acesso' um processo de Excel aberto, qualquer instância pendurado perdido de Excel será removido. Os abaixo código simplesmente verifica se há processos em que o nome é 'Excel', em seguida, escreve a propriedade MainWindowTitle do processo para uma string. Este 'interação' com o processo parece fazer captura do Windows para cima e abortar a instância congelada de Excel.

eu executar o abaixo método pouco antes das quite-add em que estou desenvolvendo, como ele dispara descarga evento. Ele remove todas as instâncias suspensos da Excel de cada vez. Em toda a honestidade que eu não sou inteiramente certo por que isso funciona, mas funciona bem para mim e poderia ser colocado no final de qualquer aplicativo Excel sem ter que se preocupar com pontos duplos, Marshal.ReleaseComObject, nem processos matando. Eu estaria muito interessado em quaisquer sugestões de que esta é eficaz.

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

Eu acho que alguns de que é apenas a maneira que o quadro trata aplicativos do Office, mas posso estar errado. Em alguns dias, algumas aplicações limpar os processos imediatamente, e outros dias, parece que esperar até o fechamento de aplicativos. Em geral, eu parei de prestar atenção aos detalhes e apenas se certificar de que não existem quaisquer processos extra flutuando no final do dia.

Além disso, e talvez eu esteja mais de simplificar as coisas, mas acho que você pode apenas ...

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

Como eu disse anteriormente, eu não tendem a prestar atenção aos detalhes de quando as aparece ou desaparece processo do Excel, mas que normalmente funciona para mim. Eu também não gosto de manter Excel processa em torno de qualquer outra coisa do que a quantidade mínima de tempo, mas eu sou provavelmente apenas sendo paranóico com isso.

Como alguns provavelmente já escrito, não é apenas importante como você próximo do Excel (objeto); também é importante como você open -lo e também pelo tipo do projeto.

Em um aplicativo WPF, basicamente o mesmo código está funcionando sem ou com muito poucos problemas.

Eu tenho um projeto em que o mesmo arquivo Excel está sendo processado várias vezes para diferentes valor do parâmetro - por exemplo, analisá-lo com base em valores dentro de uma lista genérica.

Eu coloquei todas as funções relacionadas com o Excel para a classe base, e analisador em uma subclasse (diferentes analisadores usar funções comuns do Excel). Eu não queria que o Excel é aberta e fechada novamente para cada item em uma lista genérica, então eu abri-lo apenas uma vez na classe base e fechá-lo na subclasse. Eu tive problemas ao mover o código em um aplicativo de desktop. Eu tentei muitas das soluções acima mencionadas. GC.Collect() já foi implementado antes, duas vezes como sugerido.

Então eu decidi que eu vou mover o código para abrir o Excel para uma subclasse. Em vez de abrir uma única vez, agora eu criar um novo objeto (classe base) e Excel aberto para cada item e fechá-lo no final. Há alguma penalidade de desempenho, mas com base em vários testes processos Excel estão fechando sem problemas (no modo de depuração), assim também os arquivos temporários são removidos. Vou continuar com o teste e escrever um pouco mais se vou obter algumas atualizações.

A linha inferior é:. Você também deve verificar o código de inicialização, especialmente se você tiver muitas classes, etc

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top