Question

J'utilise l'interopérabilité Excel en C # (ApplicationClass) et j'ai placé le code suivant dans ma clause finally:

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

Bien que ce type de travail fonctionne, le processus Excel.exe est toujours en arrière-plan, même après la fermeture d'Excel. Il n'est publié que lorsque mon application est fermée manuellement.

Qu'est-ce que je fais de travers ou existe-t-il une solution de rechange pour que les objets interop soient correctement éliminés?

Était-ce utile?

La solution

Excel ne se ferme pas car votre application contient toujours des références à des objets COM.

Je suppose que vous appelez au moins un membre d'un objet COM sans l'affecter à une variable.

Pour moi, c’est l’objet excelApp.Worksheets que j’ai directement utilisé sans l’affecter à une variable:

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

Je ne savais pas qu'en interne, C # avait créé un wrapper pour l'objet COM Feuilles de calcul qui n'avait pas été publié par mon code (car je ne le savais pas) et était la raison pour laquelle Excel n'a pas été déchargé.

J'ai trouvé la solution à mon problème sur cette page , qui contient également une règle intéressante pour l'utilisation des objets COM en C #:

  

N'utilisez jamais deux points avec des objets COM.

Donc, avec cette connaissance, la bonne façon de procéder est la suivante:

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

MISE À JOUR POST MORTEM:

Je souhaite que tous les lecteurs lisent très attentivement cette réponse de Hans Passant, qui explique le piège dans lequel de nombreux développeurs sont tombés. Lorsque j'ai écrit cette réponse il y a des années, je ne connaissais pas l'effet du débogueur sur le ramasse-miettes et j'ai tiré des conclusions erronées. Je garde ma réponse inchangée pour des raisons d’histoire, mais veuillez lire ce lien et ne pas suivre la voie & "; Les deux points &"; Présentation de la récupération de place dans .NET et Nettoyage des objets d'interopérabilité Excel avec IDisposable

Autres conseils

Vous pouvez réellement libérer votre objet Application Excel proprement, mais vous devez faire attention.

Le conseil de conserver une référence nommée pour absolument chaque objet COM auquel vous accédez, puis de le publier explicitement via Marshal.FinalReleaseComObject() est correct en théorie, mais, malheureusement, très difficile à gérer en pratique. Si on glisse n'importe où et utilise & "Deux points &"; Ou itère les cellules via une boucle for each ou tout autre type de commande similaire, vous aurez des objets COM non référencés et vous exposez à un blocage. . Dans ce cas, il n'y aurait aucun moyen de trouver la cause dans le code; vous auriez à revoir tout votre code à l'œil nu et, espérons-le, à trouver la cause, tâche qui pourrait être presque impossible pour un projet de grande envergure.

La bonne nouvelle est qu’il n’est pas nécessaire de conserver une référence de variable nommée à chaque objet COM que vous utilisez. À la place, appelez GC.Collect() puis GC.WaitForPendingFinalizers() pour libérer tous les objets (généralement mineurs) auxquels vous ne possédez pas de référence, puis relâchez explicitement les objets pour lesquels vous détenez une référence de variable nommée.

Vous devez également publier vos références nommées dans l'ordre inverse de leur importance: les objets de plage en premier, puis les feuilles de calcul, les classeurs, puis enfin votre objet Application Excel.

Par exemple, en supposant que vous disposiez d'une variable d'objet Range nommée xlRng, d'une variable de feuille de calcul nommée xlSheet, d'une variable de classeur nommée xlBook et d'une variable d'application Excel nommée xlApp, votre code de nettoyage pourrait alors ressembler quelque chose comme ce qui suit:

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

Dans la plupart des exemples de code que vous verrez pour nettoyer les objets COM à partir de .NET, les appels <=> et <=> sont passés DEUX FOIS comme dans:

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

Cela ne devrait toutefois pas être nécessaire, sauf si vous utilisez Visual Studio Tools pour Office (VSTO), qui utilise des finaliseurs qui permettent de promouvoir un graphique entier d'objets dans la file d'attente de finalisation. De tels objets ne seront pas libérés avant la récupération de place next . Cependant, si vous n'utilisez pas VSTO, vous devriez pouvoir appeler <=> et <=> une seule fois.

Je sais que le fait d'appeler explicitement <=> est un non-non (et le faire deux fois semble très pénible), mais il n'y a pas moyen de le contourner, pour être honnête. Par le biais d’opérations normales, vous allez générer des objets cachés sur lesquels vous ne possédez aucune référence que vous ne pouvez donc libérer par un autre moyen que d’appeler <=>.

.

C’est un sujet complexe, mais c’est tout ce qu’il ya à faire. Une fois que vous avez établi ce modèle pour votre procédure de nettoyage, vous pouvez coder normalement, sans avoir besoin de wrappers, etc.: -)

J'ai un tutoriel à ce sujet ici:

Automatisation des programmes Office avec Interop VB.Net / COM

C’est écrit pour VB.NET, mais ne vous découragez pas, les principes sont exactement les mêmes que lorsque vous utilisez C #.

Préface: ma réponse contient deux solutions. Soyez donc prudent lorsque vous lisez et ne manquez rien.

Il existe différentes manières et des conseils sur la manière de décharger une instance Excel, tels que:

  • Libérer explicitement chaque objet com avec Marshal.FinalReleaseComObject () (sans oublier implicitement objets créés). Libérer chaque objet com créé, vous pouvez utiliser la règle des 2 points mentionnée ici:
    Comment nettoyer correctement l'interopérabilité Excel des objets?

  • Appel de GC.Collect () et GC.WaitForPendingFinalizers () à faire CLR libère les objets com inutilisés * (en fait, cela fonctionne; voir ma deuxième solution pour plus de détails)

  • Vérifier si l'application serveur-com montre peut-être une boîte de message en attente de l'utilisateur de répondre (bien que je ne sois pas sûr que cela peut empêcher Excel de fermer, mais j'en ai entendu parler quelques fois)

  • Envoi du message WM_CLOSE au principal Fenêtre Excel

  • Exécuter la fonction qui fonctionne avec Excel dans un AppDomain séparé. Certaines personnes croient qu'une instance d'Excel sera fermé, lorsque AppDomain sera déchargé.

  • Suppression de toutes les instances Excel instanciées après le démarrage de notre code Excel.

MAIS! Parfois, toutes ces options ne vous aident pas ou ne peuvent pas convenir!

Par exemple, hier, j'ai découvert que l'une de mes fonctions (qui fonctionne avec Excel) continue à s'exécuter après la fin de la fonction. J'ai tout essayé! J'ai soigneusement vérifié l'ensemble de la fonction 10 fois et ajouté Marshal.FinalReleaseComObject () pour tout! J'ai également eu GC.Collect () et GC.WaitForPendingFinalizers (). J'ai vérifié les boîtes de message cachées. J'ai essayé d'envoyer un message WM_CLOSE à la fenêtre principale d'Excel. J'ai exécuté ma fonction dans un AppDomain séparé et déchargé ce domaine. Rien n'a aidé! L'option permettant de fermer toutes les instances Excel est inappropriée, car si l'utilisateur démarre une autre instance Excel manuellement, lors de l'exécution de ma fonction qui fonctionne également avec Excel, cette instance sera également fermée par ma fonction. Je parie que l'utilisateur ne sera pas heureux! Donc, honnêtement, c’est une option boiteuse (pas d'offensive les gars). J'ai donc passé quelques heures avant de trouver une (à mon humble avis) solution : Tuez le processus Excel par sa fenêtre principale (c'est la première solution).

Voici le code simple:

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

Comme vous pouvez le constater, j'ai fourni deux méthodes, selon le modèle Try-Parse (je pense que cela convient ici): une méthode ne renvoie pas l'exception si le processus ne peut pas être tué (par exemple, le processus ne existe plus) et une autre méthode lève l'exception si le processus n'a pas été tué. Le seul endroit faible de ce code concerne les autorisations de sécurité. Théoriquement, l'utilisateur peut ne pas avoir l'autorisation de tuer le processus, mais dans 99,99% des cas, il dispose de telles autorisations. Je l’ai également testée avec un compte d’invité - cela fonctionne parfaitement.

Ainsi, votre code, fonctionnant avec Excel, peut ressembler à ceci:

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 est terminé! :)

D'accord, revenons à la deuxième solution, comme je l'avais promis au début du post. La deuxième solution consiste à appeler GC.Collect () et GC.WaitForPendingFinalizers (). Oui, elles fonctionnent réellement, mais vous devez faire attention ici!
Beaucoup de gens disent (et j'ai dit) que l'appel de GC.Collect () n'aide pas. Mais la raison pour laquelle cela n’aiderait pas, c’est qu’il y ait toujours des références aux objets COM! L'une des raisons les plus courantes pour lesquelles GC.Collect () n'est pas utile est l'exécution du projet en mode débogage. En mode débogage, les objets qui ne sont plus vraiment référencés ne seront pas récupérés jusqu'à la fin de la méthode.
Donc, si vous avez essayé GC.Collect () et GC.WaitForPendingFinalizers () et que cela n’a pas aidé, essayez de procéder comme suit:

1) Essayez d’exécuter votre projet en mode Release et vérifiez si Excel est fermé correctement

2) Enveloppez la méthode d'utilisation d'Excel dans une méthode distincte. Donc, au lieu de quelque chose comme ça:

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

vous écrivez:

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

Maintenant, Excel va fermer =)

UPDATE : code C # ajouté et lien vers les tâches Windows

J’ai passé quelque temps à essayer de résoudre ce problème, et à l’époque XtremeVBTalk était le plus actif et le plus réactif. Fermeture propre d'un processus Excel Interop , même si votre application se bloque . Vous trouverez ci-dessous un résumé du message et le code copié dans ce message.

  • La fermeture du processus d'interopérabilité avec Application.Quit() et Process.Kill() fonctionne généralement, mais échoue si les applications se bloquent de manière catastrophique. C'est à dire. si l'application se bloque, le processus Excel sera toujours en cours d'exécution.
  • La solution consiste à laisser le système d'exploitation gérer le nettoyage de vos processus via Objets de travail Windows utilisant des appels Win32. À la mort de votre application principale, les processus associés (Excel) seront également interrompus.

J’ai trouvé cette solution propre, car le système d’exploitation effectue un véritable travail de nettoyage. Tout ce que vous avez à faire est d’enregistrer le processus Excel.

Code du travail Windows

Termine les appels de l'API Win32 pour enregistrer les processus 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);
    }

}

Note sur le code du constructeur

  • Dans le constructeur, le info.LimitFlags = 0x2000; est appelé. 0x2000 est la JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE valeur enum et cette valeur est définie par MSDN en tant que:
  

Arrête tous les processus associés au travail lorsque le   la dernière poignée du travail est fermée.

Appel d'API Win32 supplémentaire pour obtenir l'ID de processus (PID)

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

Utilisation du code

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

Cela a fonctionné pour un projet sur lequel je travaillais:

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

Nous avons appris qu'il était important de définir chaque référence à un objet COM Excel sur la valeur null lorsque vous en avez terminé. Cela incluait les cellules, les feuilles et tout le reste.

Tout ce qui se trouve dans l'espace de noms Excel doit être publié. Période

Vous ne pouvez pas faire:

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

Vous devez faire

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

suivi de la libération des objets.

Tout d'abord, vous jamais devez appeler Marshal.ReleaseComObject(...) ou Marshal.FinalReleaseComObject(...) lorsque vous utilisez Excel Interop. C'est un anti-modèle déroutant, mais toute information à ce sujet, y compris de Microsoft, qui indique que vous devez libérer manuellement les références COM à partir de .NET est incorrecte. Le fait est que le runtime .NET et le ramasse-miettes gardent correctement trace et nettoient les références COM. Pour votre code, cela signifie que vous pouvez supprimer la boucle `while (...) entière en haut.

Deuxièmement, si vous souhaitez vous assurer que les références COM à un objet COM en dehors du processus sont nettoyées à la fin de votre processus (afin que le processus Excel se ferme), vous devez vous assurer que le ramasse-miettes s'exécute. Vous faites cela correctement avec les appels à GC.Collect() et à GC.WaitForPendingFinalizers(). Le fait d'appeler deux fois est sûr, et garantit que les cycles sont également nettoyés (bien que je ne sois pas sûr que cela soit nécessaire et apprécierais un exemple qui le montre bien.)

Troisièmement, lors de l'exécution sous le débogueur, les références locales seront artificiellement maintenues en vie jusqu'à la fin de la méthode (pour que l'inspection des variables locales fonctionne). Ainsi, les appels rng.Cells ne sont pas efficaces pour nettoyer un objet tel que <=> à partir de la même méthode. Vous devez séparer le code effectuant l'interopérabilité COM du nettoyage du GC en méthodes distinctes. (Ce fut une découverte clé pour moi, à partir d'une partie de la réponse publiée ici par @nightcoder.)

Le modèle général serait donc:

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

Il y a beaucoup de fausses informations et de confusion à propos de ce problème, y compris de nombreuses publications sur MSDN et sur Stack Overflow (et plus particulièrement sur cette question!).

Ce qui m'a finalement convaincu de regarder de plus près et de trouver le bon conseil, c'est l'article de blog Marshal.ReleaseComObject Considéré comme dangereux ainsi que la recherche du problème avec des références conservées sous le débogueur qui confondait mes tests précédents.

I trouvé a Modèle générique utile pouvant aider à mettre en œuvre le modèle d'élimination correct pour les objets COM, qui ont besoin de Marshal.ReleaseComObject lorsqu'ils sont hors de portée:

Utilisation:

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

Modèle:

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

Référence:

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

Je ne peux pas croire que ce problème hante le monde depuis 5 ans ... Si vous avez créé une application, vous devez la fermer avant de supprimer le lien.

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

lors de la fermeture

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

Lorsque vous créez une application Excel, un programme Excel s’ouvre en arrière-plan. Avant de libérer le lien, vous devez ordonner à ce programme excel de quitter, car ce programme excel ne fait pas partie de votre contrôle direct. Par conséquent, il restera ouvert si le lien est libéré!

Bonne programmation tout le monde ~~

Développeurs communs, aucune de vos solutions ne fonctionnait pour moi, alors je décide de mettre en œuvre une nouvelle astuce .

Commençons par spécifier & "Quel est notre objectif? &"; = > " Ne pas voir d’objet Excel après notre travail dans le gestionnaire de tâches "

Ok. Ne laissez pas non plus le contester et commencer à le détruire, mais songez à ne pas détruire les autres instances d'Excel fonctionnant en parallèle.

Alors, obtenez la liste des processeurs actuels et récupérez le PID des processus EXCEL, puis une fois votre travail terminé, nous avons un nouvel invité dans la liste des processus avec un PID unique. Trouvez-le et détruisez-le.

< Gardez à l'esprit que tout nouveau processus Excel pendant votre travail Excel sera détecté comme nouveau et détruit >  < Une meilleure solution consiste à capturer le PID du nouvel objet Excel créé et à simplement détruire ce & Gt;

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

Ceci résout mon problème, espérons que le vôtre aussi.

Cela semble être comme si c'était trop compliqué. D'après mon expérience, il n'y a que trois choses clés pour bien fermer Excel:

1: assurez-vous qu'il ne reste aucune référence à l'application Excel que vous avez créée (vous ne devriez en avoir qu'une seule; définissez-la sur null)

2: appelez GC.Collect()

3: Excel doit être fermé, soit en fermant manuellement le programme, soit en appelant Quit sur l'objet Excel. (Notez que GC.WaitForPendingFinalizers() fonctionnera comme si l'utilisateur tentait de fermer le programme et présenterait une boîte de dialogue de confirmation s'il y avait des modifications non enregistrées, même si Excel n'était pas visible. L'utilisateur pouvait appuyer sur annuler, puis Excel ne serait pas été fermé.)

1 doit se produire avant 2, mais 3 peut arriver à tout moment.

Une façon d'implémenter cela consiste à encapsuler l'objet Excel interop avec votre propre classe, à créer l'instance d'interop dans le constructeur et à implémenter IDisposable avec Dispose à la recherche de quelque chose comme

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

Cela nettoiera Excel du point de vue de votre programme. Une fois Excel fermé (manuellement par l'utilisateur ou par appelez <=>), le processus disparaîtra. Si le programme a déjà été fermé, le processus disparaîtra lors de l'appel <=>.

(Je ne sais pas à quel point c'est important, mais vous voudrez peut-être passer un <=> appel après l'appel <=> mais il n'est pas strictement nécessaire de vous débarrasser du processus Excel.)

Cela a fonctionné pour moi sans problème pendant des années. Gardez à l'esprit que, même si cela fonctionne, vous devez en fait fermer en douceur pour que cela fonctionne. Vous obtiendrez toujours une accumulation de processus excel.exe si vous interrompez votre programme avant le nettoyage d’Excel (généralement en appuyant sur & "; Arrêter &"; Pendant le débogage de votre programme).

Pour ajouter aux raisons pour lesquelles Excel ne se ferme pas, même lorsque vous créez des références directes à chaque objet lors de la lecture, la création est la boucle 'Pour'.

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

J'ai toujours suivi les conseils donnés dans la réponse de VVS . Cependant, afin de maintenir cette réponse à jour avec les dernières options, je pense que tous mes projets futurs utiliseront le & "NetOffice &"; bibliothèque.

NetOffice est un remplacement complet des PIA d'Office et est totalement indépendant de la version. Il s’agit d’une collection d’emballeurs COM gérés pouvant gérer le nettoyage qui cause souvent de tels maux de tête lorsqu’on travaille avec Microsoft Office dans .NET.

Certaines fonctionnalités clés sont:

  • Généralement indépendant de la version (et les fonctionnalités dépendantes de la version sont documentées)
  • Aucune dépendance
  • Pas d'EFP
  • Aucune inscription
  • pas de VSTO

Je ne suis aucunement affilié au projet. J'apprécie vraiment la réduction drastique des maux de tête.

La réponse acceptée ici est correcte, mais notez également que non seulement & "deux points &"; les références doivent être évitées, mais également les objets récupérés via l'index. Vous n'avez pas non plus besoin d'attendre la fin du programme pour nettoyer ces objets, il est préférable de créer des fonctions qui les nettoieront dès que vous en aurez fini, dans la mesure du possible. Voici une fonction que j'ai créée qui attribue certaines propriétés d'un objet Style appelé 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 { } 
}

Remarquez que je devais définir xlBorders[Excel.XlBordersIndex.xlEdgeBottom] sur une variable pour la nettoyer (pas à cause des deux points, qui font référence à une énumération qui n'a pas besoin d'être libérée, mais à cause de l'objet auquel je fais référence. est en réalité un objet frontière qui doit être relâché).

Ce genre de chose n’est pas vraiment nécessaire dans les applications standard, qui nettoient très bien elles-mêmes, mais dans les applications ASP.NET, si vous en oubliez une seule, peu importe la fréquence à laquelle vous appelez le ramasse-miettes. , Excel sera toujours en cours d'exécution sur votre serveur.

Le contrôle du gestionnaire de tâches lors de l'écriture de ce code nécessite beaucoup d'attention aux détails et de nombreuses exécutions de test. Cela vous évite d'avoir à chercher désespérément dans les pages de code pour trouver l'instance manquante. Ceci est particulièrement important lorsque vous travaillez dans des boucles, où vous devez libérer CHAQUE INSTANCE d'un objet, même s'il utilise le même nom de variable à chaque boucle.

Après avoir essayé

  1. Libérez les objets COM dans l'ordre inverse
  2. Ajoutez GC.Collect() et GC.WaitForPendingFinalizers() deux fois à la fin
  3. Pas plus de deux points
  4. Fermez le classeur et quittez l'application
  5. Exécuter en mode de lancement

la solution finale qui fonctionne pour moi est de déplacer un ensemble de

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

que nous avons ajoutés à la fin de la fonction dans un wrapper, comme suit:

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

J'ai suivi cela exactement ... Mais j'ai quand même rencontré des problèmes 1 fois sur 1000. Qui sait pourquoi Il est temps de sortir le marteau ...

Dès que la classe d'application Excel est instanciée, je récupère le processus Excel qui vient d'être créé.

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

Ensuite, une fois que j'ai effectué tous les nettoyage COM ci-dessus, je m'assure que ce processus ne fonctionne pas. S'il est toujours en cours d'exécution, tuez-le!

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

& # 168; & # 176; & # 186; & # 164; & # 248; & # 8222; & # 184; Tirez Excel proc et mâchez du chewing-gum & # 184; & # 8222; & # 248; & # 164; & # 186; & # 176; & # 168;

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

Vous devez savoir qu'Excel est également très sensible à la culture dans laquelle vous vous trouvez.

Vous devrez peut-être définir la culture sur EN-US avant d'appeler des fonctions Excel. Cela ne s'applique pas à toutes les fonctions, mais à certaines d'entre elles.

    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;

Cela s'applique même si vous utilisez VSTO.

Pour plus de détails: http://support.microsoft.com /default.aspx?scid=kb;en-us;Q320369

& "N'utilisez jamais deux points avec des objets COM &"; est une bonne règle pour éviter les fuites de références COM, mais Excel PIA peut entraîner des fuites de plus de façons qu’apparentes à première vue.

L'une de ces méthodes consiste à s'abonner à tout événement exposé par l'un des objets COM du modèle objet Excel.

Par exemple, vous abonner à l'événement WorkbookOpen de la classe Application.

Un peu de théorie sur les événements COM

Les classes COM exposent un groupe d'événements via des interfaces de rappel. Pour s'abonner à des événements, le code client peut simplement enregistrer un objet implémentant l'interface de rappel et la classe COM appelle ses méthodes en réponse à des événements spécifiques. Etant donné que l’interface de rappel est une interface COM, il est du devoir de l’objet d’implémentation de décrémenter le compte de références de tout objet COM qu’il reçoit (en tant que paramètre) pour l’un des gestionnaires d’événements.

Comment Excel PIA expose les événements COM

Excel PIA expose les événements COM de la classe d’application Excel en tant qu’événements .NET classiques. Chaque fois que le code client s'abonne à un événement .NET (accent mis sur "a"), PIA crée une instance d'une classe implémentant l'interface de rappel et l'enregistre dans Excel. .

Ainsi, un certain nombre d'objets de rappel sont enregistrés avec Excel en réponse à différentes demandes d'abonnement à partir du code .NET. Un objet de rappel par abonnement à un événement.

Une interface de rappel pour la gestion des événements signifie que PIA doit s'abonner à tous les événements d'interface pour chaque demande d'abonnement aux événements .NET. Il ne peut pas choisir et choisir. À la réception d'un rappel d'événement, l'objet de rappel vérifie si le gestionnaire d'événements .NET associé est intéressé par l'événement en cours ou non, puis appelle le gestionnaire ou ignore silencieusement le rappel.

Effet sur le nombre de références d'instance COM

Tous ces objets de rappel ne décrémentent pas le compte de référence des objets COM qu’ils reçoivent (en tant que paramètres) pour les méthodes de rappel (même pour ceux qui sont ignorés de manière silencieuse). Ils comptent uniquement sur le CLR pour libérer les objets COM.

Etant donné que l'exécution du CPG n'est pas déterministe, cela peut entraîner le blocage du processus Excel pendant une durée plus longue que celle souhaitée et créer une impression de "fuite de mémoire".

Solution

Pour l’instant, la seule solution consiste à éviter le fournisseur d’événements de PIA & # 8217; s pour la classe COM et à écrire votre propre fournisseur d’événements qui libère des objets COM de manière déterministe.

Pour la classe Application, cela peut être fait en implémentant l'interface AppEvents puis en enregistrant l'implémentation avec Excel en utilisant Interface IConnectionPointContainer . La classe Application (ainsi que tous les objets COM exposant des événements à l'aide du mécanisme de rappel) implémente l'interface IConnectionPointContainer.

Comme d'autres l'ont fait remarquer, vous devez créer une référence explicite pour chaque objet Excel utilisé et appeler Marshal.ReleaseComObject sur cette référence, comme décrit dans cet article de la Base de connaissances . Vous devez également utiliser try / finally pour vous assurer que ReleaseComObject est toujours appelé, même lorsqu'une exception est levée. C'est à dire. au lieu de:

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

vous devez faire quelque chose comme:

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

Vous devez également appeler Application.Quit avant de libérer l'objet Application si vous souhaitez que Excel soit fermé.

Comme vous pouvez le constater, cela devient rapidement extrêmement difficile à manier dès que vous essayez de faire quelque chose, même moyennement complexe. J'ai développé avec succès des applications .NET avec une simple classe wrapper qui encapsule quelques manipulations simples du modèle objet Excel (ouvrir un classeur, écrire dans une plage, enregistrer / fermer le classeur, etc.). La classe wrapper implémente IDisposable, implémente soigneusement Marshal.ReleaseComObject sur chaque objet qu’elle utilise et n’expose publiquement aucun objet Excel au reste de l’application.

Mais cette approche ne s'adapte pas bien aux exigences plus complexes.

C’est un gros défaut de .NET COM Interop. Pour des scénarios plus complexes, j’envisagerais sérieusement d’écrire une DLL ActiveX en VB6 ou dans un autre langage non géré auquel vous pouvez déléguer toutes les interactions avec des objets COM hors-processus tels que Office. Vous pouvez ensuite référencer cette DLL ActiveX à partir de votre application .NET, ce qui sera beaucoup plus facile, car vous n’auriez besoin que de publier cette référence.

Lorsque tout ce qui précède ne fonctionne pas, essayez de laisser à Excel le temps de fermer ses feuilles:

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

...
FinalReleaseComObject(app);

Assurez-vous de libérer tous les objets liés à Excel!

J'ai passé quelques heures à essayer de plusieurs façons. Toutes les idées sont bonnes mais j'ai finalement trouvé mon erreur: Si vous ne libérez pas tous les objets, aucune des méthodes ci-dessus ne peut vous aider comme dans mon cas. Assurez-vous de libérer tous les objets, y compris le premier intervalle!

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

Les options sont ensemble ici .

La règle des deux points ne fonctionnait pas pour moi. Dans mon cas, j'ai créé une méthode pour nettoyer mes ressources comme suit:

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

Vous devez faire très attention en utilisant des applications d'interopérabilité Word / Excel. Après avoir essayé toutes les solutions, il restait encore beaucoup de & «WinWord &»; processus laissé ouvert sur le serveur (avec plus de 2000 utilisateurs).

Après avoir travaillé sur le problème pendant des heures, je me suis rendu compte que si j'ouvrais plusieurs documents simultanément avec Word.ApplicationClass.Document.Open() plusieurs threads, le processus de travail IIS (w3wp.exe) se bloquait, laissant tous les processus WinWord ouverts!

Donc, je suppose qu'il n'y a pas de solution absolue à ce problème, mais que vous passiez à d'autres méthodes telles que Office Open Développement XML .

Un bon article sur la libération des objets COM est 2.5 Libération des objets COM (MSDN).

La méthode que je préconiserais consiste à annuler vos références Excel.Interop s’il s’agit de variables non locales, puis à appeler GC.Collect() et GC.WaitForPendingFinalizers() à deux reprises. Les variables Interop à portée locale seront traitées automatiquement.

Cela évite de conserver une référence nommée pour chaque objet COM.

Voici un exemple tiré de l'article:

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

Ces mots sont tirés directement de l'article:

  

Dans presque toutes les situations, l'annulation de la référence RCW et le forçage d'une récupération de place nettoient correctement. Si vous appelez également GC.WaitForPendingFinalizers, le garbage collection sera aussi déterministe que vous pourrez le faire. Autrement dit, vous serez à peu près sûr de savoir exactement quand l'objet aura été nettoyé & # 8212; au retour du deuxième appel de WaitForPendingFinalizers. Vous pouvez également utiliser Marshal.ReleaseComObject. Cependant, notez qu'il est très peu probable que vous utilisiez cette méthode.

Ma solution

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

La réponse acceptée n'a pas fonctionné pour moi. Le code suivant dans le destructeur a fait le travail.

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

Je travaille actuellement sur la bureautique et je suis tombé sur une solution qui fonctionne à chaque fois pour moi. C’est simple et n’implique aucune destruction de processus.

Il semble qu'en supprimant simplement les processus actifs actuels et en "accédant" à un processus Excel ouvert, toutes les instances suspendues parasites d'Excel soient supprimées. Le code ci-dessous recherche simplement les processus dont le nom est 'Excel', puis écrit la propriété MainWindowTitle du processus dans une chaîne. Cette "interaction" avec le processus semble amener Windows à rattraper son retard et à abandonner l'instance gelée d'Excel.

J'exécute la méthode ci-dessous juste avant que le complément que je développe soit arrêté, car il déclenche l'événement de déchargement. Il supprime chaque instance suspendue d'Excel à chaque fois. En toute honnêteté, je ne suis pas tout à fait sûr de savoir pourquoi cela fonctionne, mais cela fonctionne bien pour moi et pourrait être placé à la fin de toute application Excel sans avoir à vous soucier des points doubles, Marshal.ReleaseComObject, ni des processus de mise à mort. Je serais très intéressé par toute suggestion quant à la raison pour laquelle cela est efficace.

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

Je pense qu'une partie de cela est simplement la manière dont le framework gère les applications Office, mais je peux me tromper. Certains jours, certaines applications nettoient les processus immédiatement et d'autres jours, il semble attendre que l'application se ferme. En général, je cesse de prêter attention aux détails et je veille simplement à ce qu’aucun processus supplémentaire ne circule à la fin de la journée.

Aussi, et je simplifie peut-être les choses, mais je pense que vous pouvez simplement ...

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

Comme je l'ai dit plus tôt, je n'ai pas tendance à faire attention aux détails du moment où le processus Excel apparaît ou disparaît, mais cela fonctionne généralement pour moi. De plus, je n'aime pas garder les processus Excel plus longtemps, mais je suis probablement paranoïaque à ce sujet.

Comme certains l’ont probablement déjà écrit, la manière dont vous fermez Excel (objet) n’est pas seulement importante; il est également important de savoir comment vous ouvrez-le et en fonction du type de projet.

Dans une application WPF, le même code fonctionne sans ou avec très peu de problèmes.

J'ai un projet dans lequel le même fichier Excel est traité plusieurs fois pour une valeur de paramètre différente - par exemple. l'analyse syntaxique en fonction des valeurs d'une liste générique.

J'ai placé toutes les fonctions liées à Excel dans la classe de base et l'analyseur dans une sous-classe (différents analyseurs utilisent des fonctions Excel communes). Je ne voulais pas qu'Excel soit ouvert et refermé pour chaque élément d'une liste générique. Je ne l'ai donc ouvert qu'une fois dans la classe de base et je l'ai fermé dans la sous-classe. J'ai eu des problèmes en déplaçant le code dans une application de bureau. J'ai essayé plusieurs des solutions mentionnées ci-dessus. GC.Collect() était déjà implémenté auparavant, deux fois comme suggéré.

Ensuite, j'ai décidé de déplacer le code d'ouverture d'Excel dans une sous-classe. Au lieu de n'ouvrir qu'une seule fois, je crée maintenant un nouvel objet (classe de base), j'ouvre Excel pour chaque élément et le ferme à la fin. Il y a quelques inconvénients en termes de performances, mais sur la base de plusieurs tests, les processus Excel se ferment sans problème (en mode débogage), ainsi les fichiers temporaires sont également supprimés. Je continuerai à tester et à en écrire d'autres si j'obtiens des mises à jour.

La ligne du bas est: Vous devez également vérifier le code d’initialisation, surtout si vous avez plusieurs classes, etc.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top