Domanda

Sto usando l'interoperabilità di Excel in C# (ApplicationClass) e hanno messo il seguente codice nel mio clausola finally:

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

Anche se questo tipo di lavori, la Excel.exe il processo è ancora in background anche dopo la chiusura di Excel.È rilasciato solo una volta che la mia applicazione è chiusa manualmente.

Che cosa sto facendo di sbagliato, o c'è un'alternativa per garantire l'interoperabilità oggetti vengono correttamente smaltiti?

È stato utile?

Soluzione

Excel non si chiude perché l'applicazione contiene ancora riferimenti a oggetti COM.

Suppongo che tu stia invocando almeno un membro di un oggetto COM senza assegnarlo a una variabile.

Per me è stato l'oggetto excelApp.Worksheets che ho usato direttamente senza assegnarlo a una variabile:

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

Non sapevo che C # internamente creasse un wrapper per l'oggetto Fogli di lavoro COM che non era stato rilasciato dal mio codice (perché non ne ero a conoscenza) ed era la causa del perché Excel non è stato scaricato.

Ho trovato la soluzione al mio problema su questa pagina , che ha anche una bella regola per l'utilizzo degli oggetti COM in C #:

  

Non usare mai due punti con oggetti COM.


Quindi, con questa conoscenza, il modo giusto di fare quanto sopra è:

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

AGGIORNAMENTO POST MORTEM:

Voglio che tutti i lettori leggano attentamente questa risposta di Hans Passant in quanto spiega la trappola nella quale io e molti altri sviluppatori siamo incappati. Quando ho scritto questa risposta anni fa non sapevo degli effetti del debugger sul garbage collector e ho tratto conclusioni errate. Mantengo la mia risposta inalterata per il bene della storia, ma per favore leggi questo link e non segui la strada di & Quot; i due punti & Quot ;: Comprensione della garbage collection in .NET e

Altri suggerimenti

Puoi effettivamente rilasciare l'oggetto Applicazione Excel in modo pulito, ma devi fare attenzione.

Il consiglio di mantenere un riferimento denominato per qualsiasi oggetto COM a cui si accede e quindi di rilasciarlo esplicitamente tramite Marshal.FinalReleaseComObject() è corretto in teoria, ma, sfortunatamente, molto difficile da gestire in pratica. Se uno scivola mai da qualche parte e usa & Quot; due punti & Quot; oppure esegue l'iterazione di celle tramite un ciclo for each o qualsiasi altro tipo di comando simile, allora avrai oggetti COM senza riferimento e rischierai un blocco . In questo caso, non ci sarebbe modo di trovare la causa nel codice; dovresti rivedere a occhio tutto il tuo codice e sperare di trovare la causa, un compito che potrebbe essere quasi impossibile per un grande progetto.

La buona notizia è che in realtà non è necessario mantenere un riferimento alla variabile denominata per ogni oggetto COM che si utilizza. Chiamare invece GC.Collect() e quindi GC.WaitForPendingFinalizers() per rilasciare tutti gli oggetti (generalmente minori) su cui non si detiene un riferimento, quindi rilasciare esplicitamente gli oggetti su cui si detiene un riferimento a una variabile con nome.

Dovresti anche rilasciare i riferimenti nominati in ordine inverso di importanza: prima intervallo oggetti, quindi fogli di lavoro, cartelle di lavoro e infine l'oggetto Applicazione Excel.

Ad esempio, supponendo che tu avessi una variabile oggetto Range denominata xlRng, una variabile del foglio di lavoro denominata xlSheet, una variabile Workbook denominata xlBook e una variabile dell'applicazione Excel denominata xlApp, il tuo codice di pulizia potrebbe apparire qualcosa di simile al seguente:

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

Nella maggior parte degli esempi di codice che vedrai per ripulire gli oggetti COM da .NET, le chiamate <=> e <=> vengono effettuate DUE VOLTE come in:

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

Ciò non dovrebbe essere richiesto, tuttavia, a meno che non si utilizzi Visual Studio Tools for Office (VSTO), che utilizza finalizzatori che promuovono un intero grafico di oggetti nella coda di finalizzazione. Tali oggetti non sarebbero stati rilasciati fino alla next garbage collection. Tuttavia, se non si utilizza VSTO, si dovrebbe essere in grado di chiamare <=> e <=> solo una volta.

So che chiamare esplicitamente <=> è un no-no (e certamente farlo due volte suona molto doloroso), ma non c'è modo di aggirarlo, a dire il vero. Attraverso le normali operazioni genererai oggetti nascosti ai quali non detieni alcun riferimento che, pertanto, non puoi rilasciare con altri mezzi oltre a chiamare <=>.

Questo è un argomento complesso, ma questo è davvero tutto quello che c'è da fare. Una volta stabilito questo modello per la procedura di pulizia, è possibile codificare normalmente, senza la necessità di wrapper, ecc. :-)

Ho un tutorial su questo qui:

Automatizzare i programmi di Office con VB.Net / COM Interop

È scritto per VB.NET, ma non scoraggiarlo, i principi sono esattamente gli stessi di quando si usa C #.

Prefazione: la mia risposta contiene due soluzioni, quindi fai attenzione durante la lettura e non perdere nulla.

Esistono diversi modi e consigli su come scaricare l'istanza di Excel, come ad esempio:

  • Rilascio esplicito di OGNI oggetto com con Marshal.FinalReleaseComObject () (senza dimenticare implicitamente creato com-object). Rilasciare puoi creare ogni oggetto com creato la regola dei 2 punti menzionati qui:
    Come si pulisce correttamente l'interoperabilità di Excel oggetti?

  • Chiamata GC.Collect () e GC.WaitForPendingFinalizers () da realizzare CLR rilascia gli oggetti com inutilizzati * (In realtà, funziona, vedi la mia seconda soluzione per i dettagli)

  • Verifica se com-server-application forse mostra una finestra di messaggio in attesa l'utente a rispondere (anche se non lo sono sicuro che può impedire a Excel di chiudendo, ma ne ho sentito parlare alcuni volte)

  • Invio del messaggio WM_CLOSE al principale Finestra di Excel

  • Esecuzione della funzione che funziona con Excel in un AppDomain separato. Alcune persone credono all'istanza di Excel verrà chiuso, quando AppDomain è scaricato.

  • Uccidere tutte le istanze di Excel che sono state istanziate dopo l'avvio del nostro codice di interoperabilità di Excel.

MA! A volte tutte queste opzioni non aiutano o non possono essere appropriate!

Ad esempio, ieri ho scoperto che in una delle mie funzioni (che funziona con Excel) Excel continua a funzionare al termine della funzione. Ho provato di tutto! Ho controllato a fondo l'intera funzione 10 volte e ho aggiunto Marshal.FinalReleaseComObject () per tutto! Ho anche avuto GC.Collect () e GC.WaitForPendingFinalizers (). Ho controllato le finestre di messaggio nascoste. Ho provato a inviare il messaggio WM_CLOSE alla finestra principale di Excel. Ho eseguito la mia funzione in un AppDomain separato e scaricato quel dominio. Niente ha aiutato! L'opzione con la chiusura di tutte le istanze di Excel è inappropriata, perché se l'utente avvia manualmente un'altra istanza di Excel, durante l'esecuzione della mia funzione che funziona anche con Excel, anche quell'istanza verrà chiusa dalla mia funzione. Scommetto che l'utente non sarà felice! Quindi, onestamente, questa è un'opzione zoppa (senza offesa ragazzi). Quindi ho trascorso un paio d'ore prima di trovare una buona soluzione (a mio modesto parere) : Termina il processo Excel da hWnd della sua finestra principale (è la prima soluzione).

Ecco il codice semplice:

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

Come puoi vedere, ho fornito due metodi, secondo il modello Try-Parse (penso che sia appropriato qui): un metodo non genera l'eccezione se il Processo non può essere ucciso (ad esempio il processo non esiste più) e un altro metodo genera l'eccezione se il processo non è stato ucciso. L'unico punto debole in questo codice sono le autorizzazioni di sicurezza. Teoricamente, l'utente potrebbe non disporre delle autorizzazioni per terminare il processo, ma nel 99,99% dei casi l'utente dispone di tali autorizzazioni. L'ho anche testato con un account ospite: funziona perfettamente.

Quindi, il tuo codice, lavorando con Excel, può assomigliare a questo:

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 è terminato! :)

Ok, torniamo alla seconda soluzione, come ho promesso all'inizio del post. La seconda soluzione è chiamare GC.Collect () e GC.WaitForPendingFinalizers (). Sì, funzionano davvero, ma devi fare attenzione qui!
Molte persone dicono (e io ho detto) che chiamare GC.Collect () non aiuta. Ma il motivo per cui non sarebbe d'aiuto è se ci sono ancora riferimenti a oggetti COM! Uno dei motivi più popolari per cui GC.Collect () non è utile è l'esecuzione del progetto in modalità Debug. Negli oggetti in modalità debug a cui non viene più realmente fatto riferimento, non verranno garbage collection fino alla fine del metodo.
Quindi, se hai provato GC.Collect () e GC.WaitForPendingFinalizers () e non è stato d'aiuto, prova a fare quanto segue:

1) Prova a eseguire il progetto in modalità Rilascio e controlla se Excel è stato chiuso correttamente

2) Avvolgere il metodo di lavorare con Excel in un metodo separato. Quindi, invece di qualcosa del genere:

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

scrivi:

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

Ora Excel chiuderà =)

AGGIORNAMENTO : aggiunto codice C # e collegamento a lavori Windows

Ho passato un po 'di tempo a cercare di capire questo problema e all'epoca XtremeVBTalk era il più attivo e reattivo. Ecco un link al mio post originale, Chiusura di un processo di interoperabilità di Excel in modo pulito , anche se l'applicazione si arresta in modo anomalo . Di seguito è riportato un riepilogo del post e il codice copiato in questo post.

  • La chiusura del processo di interoperabilità con Application.Quit() e Process.Kill() funziona per la maggior parte, ma fallisce se le applicazioni si bloccano in modo catastrofico. Cioè se l'app si arresta in modo anomalo, il processo di Excel continuerà comunque a perdere.
  • La soluzione è consentire al sistema operativo di gestire la pulizia dei processi tramite Oggetti lavoro di Windows utilizzando le chiamate Win32. Quando l'applicazione principale muore, anche i processi associati (ad esempio Excel) verranno chiusi.

Ho scoperto che questa è una soluzione pulita perché il sistema operativo sta facendo un vero lavoro di pulizia. Tutto quello che devi fare è registra il processo di Excel.

Codice lavoro di Windows

Esegue il wrapping delle chiamate API Win32 per registrare i processi di interoperabilità.

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 sul codice del costruttore

  • Nel costruttore viene chiamato info.LimitFlags = 0x2000;. 0x2000 è il JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE valore enum e questo valore è definito da MSDN come:
  

Fa terminare tutti i processi associati al lavoro quando il file   l'ultimo handle per il lavoro è chiuso.

Chiamata API Win32 aggiuntiva per ottenere l'ID processo (PID)

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

Utilizzo del codice

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

Questo ha funzionato per un progetto a cui stavo lavorando:

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

Abbiamo appreso che era importante impostare ogni riferimento a un oggetto COM di Excel su null al termine dell'operazione. Ciò includeva Celle, Fogli e tutto il resto.

Tutto ciò che si trova nello spazio dei nomi di Excel deve essere rilasciato. Periodo

Non puoi fare:

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

Devi farlo

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

seguito dal rilascio degli oggetti.

Per prima cosa, non non devi mai chiamare Marshal.ReleaseComObject(...) o Marshal.FinalReleaseComObject(...) quando esegui l'interoperabilità con Excel. È un anti-pattern confuso, ma qualsiasi informazione al riguardo, incluso da Microsoft, che indica che è necessario rilasciare manualmente i riferimenti COM da .NET non è corretta. Il fatto è che il runtime .NET e il garbage collector tengono correttamente traccia e puliscono i riferimenti COM. Per il tuo codice, questo significa che puoi rimuovere l'intero ciclo "while (...) in alto.

In secondo luogo, se si desidera assicurarsi che i riferimenti COM a un oggetto COM out-of-process vengano ripuliti al termine del processo (in modo che il processo Excel si chiuda), è necessario assicurarsi che Garbage Collector sia in esecuzione. Lo fai correttamente con le chiamate a GC.Collect() e GC.WaitForPendingFinalizers(). Chiamarlo due volte è sicuro e garantisce che anche i cicli vengano definitivamente ripuliti (anche se non sono sicuro che sia necessario, e apprezzerei un esempio che lo mostri).

In terzo luogo, quando si esegue con il debugger, i riferimenti locali verranno mantenuti artificialmente in vita fino alla fine del metodo (in modo che l'ispezione delle variabili locali funzioni). Quindi rng.Cells le chiamate non sono efficaci per la pulizia di oggetti come <=> dallo stesso metodo. È necessario dividere il codice eseguendo l'interoperabilità COM dalla pulizia del GC in metodi separati. (Questa è stata una scoperta chiave per me, da una parte della risposta pubblicata qui da @nightcoder.)

Il modello generale sarebbe quindi:

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

Ci sono molte false informazioni e confusione su questo problema, inclusi molti post su MSDN e Stack Overflow (e specialmente questa domanda!).

Ciò che alla fine mi ha convinto a dare un'occhiata più da vicino e capire il consiglio giusto è stato il post sul blog Marshal.ReleaseComObject considerato pericoloso insieme a trovare il problema con riferimenti mantenuti vivi sotto il debugger che confondeva i miei test precedenti.

I trovato a utile modello generico che può aiutare a implementare il modello di smaltimento corretto per gli oggetti COM, che necessitano di Marshal.ReleaseComObject chiamato quando escono dall'ambito:

Utilizzo:

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

Riferimento:

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

Non posso credere che questo problema abbia tormentato il mondo per 5 anni .... Se hai creato un'applicazione, devi prima spegnerla prima di rimuovere il link.

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

alla chiusura

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

Quando si introduce un'applicazione Excel, si apre un programma Excel in background. Devi comandare a quel programma Excel di uscire prima di rilasciare il collegamento perché quel programma Excel non fa parte del tuo controllo diretto. Pertanto, rimarrà aperto se il collegamento viene rilasciato!

Buona programmazione a tutti ~~

Sviluppatori comuni, nessuna delle tue soluzioni ha funzionato per me, quindi decido di implementare un nuovo trucco .

Prima di tutto specifica " Qual è il nostro obiettivo? " = Gt &; " Non vedere l'oggetto Excel dopo il nostro lavoro nel task manager "

Ok. Non lasciarti sfidare e inizia a distruggerlo, ma considera di non distruggere altre istanze di Excel eseguite in parallelo.

Quindi, ottieni l'elenco dei processori attuali e recupera il PID dei processi EXCEL, quindi una volta terminato il lavoro, abbiamo un nuovo ospite nell'elenco dei processi con un PID unico, trova e distruggi solo quello.

lt &; tenere presente che qualsiasi nuovo processo di Excel durante il processo di Excel verrà rilevato come nuovo e distrutto & GT;  lt &; Una soluzione migliore è catturare il PID del nuovo oggetto Excel creato e semplicemente distruggere quel & 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();

Questo risolve il mio problema, spero anche il tuo.

Questo sembra sicuramente complicato. Dalla mia esperienza, ci sono solo tre cose chiave per far chiudere correttamente Excel:

1: assicurati che non vi siano riferimenti rimanenti all'applicazione Excel che hai creato (dovresti comunque averne uno solo; impostalo su null)

2: chiama GC.Collect()

3: Excel deve essere chiuso dall'utente chiudendo manualmente il programma o chiamando Quit sull'oggetto Excel. (Si noti che GC.WaitForPendingFinalizers() funzionerà come se l'utente tentasse di chiudere il programma e presenterà una finestra di dialogo di conferma in caso di modifiche non salvate, anche se Excel non è visibile. L'utente può premere Annulla e Excel non avrà stato chiuso.)

1 deve accadere prima di 2, ma 3 può accadere in qualsiasi momento.

Un modo per implementarlo è avvolgere l'oggetto Excel interop con la propria classe, creare l'istanza interop nel costruttore e implementare IDisposable con Dispose simile a

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

Ciò eliminerà l'eccellenza dal lato del programma. Una volta chiuso Excel (manualmente dall'utente o chiamando <=>) il processo scomparirà. Se il programma è già stato chiuso, il processo scomparirà nella <=> chiamata.

(Non sono sicuro di quanto sia importante, ma potresti desiderare una <=> chiamata dopo la <=> chiamata ma non è strettamente necessario sbarazzarsi del processo di Excel.)

Questo ha funzionato per me senza problemi per anni. Tieni presente però che mentre funziona, in realtà devi chiudere con grazia per farlo funzionare. Continuerai ad accumulare processi excel.exe se interrompi il programma prima che Excel venga ripulito (di solito premendo & Quot; stop & Quot; mentre il tuo programma è in fase di debug).

Per aggiungere ai motivi per cui Excel non si chiude, anche quando si creano riferimenti diretti a ciascun oggetto dopo la lettura, la creazione è il ciclo "For".

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

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

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

Tradizionalmente ho seguito i consigli trovati in la risposta di VVS . Tuttavia, nel tentativo di mantenere aggiornata questa risposta con le ultime opzioni, penso che tutti i miei progetti futuri useranno il & Quot; NetOffice & Quot; biblioteca.

NetOffice è un sostituto completo delle PIA di Office ed è completamente indipendente dalla versione. È una raccolta di wrapper COM gestiti in grado di gestire la pulizia che spesso causa tali mal di testa quando si lavora con Microsoft Office in .NET.

Alcune caratteristiche chiave sono:

  • Principalmente indipendenti dalla versione (e le funzionalità dipendenti dalla versione sono documentate)
  • Nessuna dipendenza
  • No PIA
  • Nessuna registrazione
  • No VSTO

Non sono in alcun modo affiliato al progetto; Apprezzo davvero la netta riduzione del mal di testa.

La risposta accettata qui è corretta, ma anche notare che non solo " due punti " i riferimenti devono essere evitati, ma anche gli oggetti che vengono recuperati tramite l'indice. Inoltre, non è necessario attendere fino al termine del programma per ripulire questi oggetti, è consigliabile creare funzioni che li ripuliscano non appena si è terminato con essi, quando possibile. Ecco una funzione che ho creato che assegna alcune proprietà di un oggetto Style chiamato 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 { } 
}

Nota che ho dovuto impostare xlBorders[Excel.XlBordersIndex.xlEdgeBottom] su una variabile per ripulirla (non a causa dei due punti, che si riferiscono a un elenco che non ha bisogno di essere rilasciato, ma perché l'oggetto a cui mi riferisco to è in realtà un oggetto Border che deve essere rilasciato).

Questo genere di cose non è realmente necessario nelle applicazioni standard, che fanno un ottimo lavoro di pulizia dopo se stesse, ma nelle applicazioni ASP.NET, se ti manca anche una di queste, non importa quanto spesso chiami il garbage collector , Excel sarà ancora in esecuzione sul tuo server.

Richiede molta attenzione ai dettagli e molte esecuzioni di test durante il monitoraggio del Task Manager durante la scrittura di questo codice, ma in tal modo si evita il fastidio di cercare disperatamente pagine di codice per trovare l'istanza persa. Ciò è particolarmente importante quando si lavora in loop, in cui è necessario rilasciare OGNI ISTANZA di un oggetto, anche se utilizza lo stesso nome di variabile ogni volta che viene eseguito il loop.

Dopo aver provato

  1. Rilascia gli oggetti COM in ordine inverso
  2. Aggiungi GC.Collect() e GC.WaitForPendingFinalizers() due volte alla fine
  3. Non più di due punti
  4. Chiudi la cartella di lavoro ed esci dall'applicazione
  5. Esegui in modalità di rilascio

la soluzione finale che funziona per me è spostare una serie di

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

che abbiamo aggiunto alla fine della funzione a un wrapper, come segue:

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

L'ho seguito esattamente ... Ma ho ancora riscontrato problemi 1 su 1000 volte. Chissà perché. È ora di tirare fuori il martello ...

Subito dopo l'istanza della classe Applicazione Excel ottengo una sospensione del processo Excel appena creato.

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

Quindi, una volta eseguita la pulizia COM di cui sopra, mi assicuro che il processo non sia in esecuzione. Se è ancora in esecuzione, uccidilo!

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

# 168 &; # 176 &; # 186 &; # 164 &; # 248 &; # 8222 &; & # 184; Spara Proc di Excel e mastica gomma da masticare & # 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);
}

Devi essere consapevole che Excel è molto sensibile anche alla cultura in cui stai eseguendo.

Potrebbe essere necessario impostare la cultura su EN-US prima di chiamare le funzioni di Excel. Questo non si applica a tutte le funzioni, ma ad alcune di esse.

    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;

Questo vale anche se si utilizza VSTO.

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

" Non usare mai due punti con oggetti COM " è un'ottima regola empirica per evitare perdite di riferimenti COM, ma Excel PIA può portare a perdite in più modi di quanto apparente a prima vista.

Uno di questi modi è la sottoscrizione a qualsiasi evento esposto da uno qualsiasi degli oggetti COM del modello a oggetti Excel.

Ad esempio, iscrivendosi all'evento WorkbookOpen della classe Application.

Qualche teoria sugli eventi COM

Le classi COM espongono un gruppo di eventi attraverso interfacce di call-back. Per iscriversi agli eventi, il codice client può semplicemente registrare un oggetto che implementa l'interfaccia di richiamata e la classe COM invocherà i suoi metodi in risposta a eventi specifici. Poiché l'interfaccia di call-back è un'interfaccia COM, è compito dell'oggetto implementare decrementare il conteggio di riferimento di qualsiasi oggetto COM che riceve (come parametro) per qualsiasi gestore di eventi.

Come Excel PIA espone gli eventi COM

Excel PIA espone gli eventi COM della classe di applicazione Excel come eventi .NET convenzionali. Ogni volta che il codice client si iscrive a un evento .NET (enfasi su "a"), PIA crea un'istanza di una classe che implementa l'interfaccia di richiamata e la registra con Excel .

Quindi, un certo numero di oggetti di richiamata vengono registrati con Excel in risposta a diverse richieste di abbonamento dal codice .NET. Un oggetto call-back per abbonamento a un evento.

Un'interfaccia di call-back per la gestione degli eventi significa che PIA deve sottoscrivere tutti gli eventi dell'interfaccia per ogni richiesta di abbonamento agli eventi .NET. Non può scegliere. Alla ricezione di un call-back di eventi, l'oggetto call-back verifica se il gestore eventi .NET associato è interessato o meno all'evento corrente e quindi invoca il gestore o ignora silenziosamente il call-back.

Effetto sui conteggi dei riferimenti all'istanza COM

Tutti questi oggetti di richiamata non diminuiscono il conteggio dei riferimenti di nessuno degli oggetti COM che ricevono (come parametri) per nessuno dei metodi di richiamata (anche per quelli che vengono silenziosamente ignorati). Si basano esclusivamente sul CLR per liberare gli oggetti COM.

Poiché l'esecuzione di GC non è deterministica, ciò può comportare il blocco del processo di Excel per una durata superiore a quella desiderata e creare un'impressione di "perdita di memoria".

Soluzione

Al momento l'unica soluzione è quella di evitare il provider di eventi PIA & # 8217; s per la classe COM e scrivere il proprio provider di eventi che rilascia in modo deterministico oggetti COM.

Per la classe Application, questo può essere fatto implementando l'interfaccia AppEvents e quindi registrando l'implementazione con Excel utilizzando Interfaccia IConnectionPointContainer . La classe Application (e per questo motivo tutti gli oggetti COM che espongono eventi utilizzando il meccanismo di callback) implementa l'interfaccia IConnectionPointContainer.

Come altri hanno sottolineato, è necessario creare un riferimento esplicito per ogni oggetto di Excel in uso, e chiamare il Maresciallo.ReleaseComObject su quella di riferimento, come descritto in questo articolo della KB.È inoltre necessario utilizzare try/finally, per garantire ReleaseComObject è sempre chiamato, anche quando viene generata un'eccezione.I. e.invece di:

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

hai bisogno di fare qualcosa di simile:

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

È inoltre necessario chiamare Applicazione.Chiudere, prima di rilasciare l'oggetto Application, se si desidera Excel per chiudere.

Come si può vedere, questo diventa rapidamente molto ingombrante non appena si tenta di fare nulla, anche moderatamente complessi.Ho sviluppato con successo .NET applications con una semplice classe wrapper che avvolge pochi e semplici manipolazioni del modello a oggetti di Excel (apre una cartella di lavoro, scrivere un Intervallo, salvare e chiudere la cartella di lavoro ecc).La classe wrapper implementa IDisposable, con attenzione implementa il Maresciallo.ReleaseComObject su ogni oggetto che utilizza, e non pubicly esporre oggetti di Excel per il resto delle app.

Ma questo approccio non scala bene per esigenze più complesse.

Questa è una grande carenza di .NETTO di Interoperabilità COM.Per scenari più complessi, avrei seriamente in considerazione l'idea di scrivere una DLL ActiveX in VB6 o altro linguaggio non gestito a cui è possibile delegare l'interazione con proc oggetti COM, come Ufficio.Si può quindi fare riferimento a questo ActiveX DLL dal tuo .NET application, e le cose saranno molto più facile, come si avrà solo bisogno di rilasciare questo riferimento.

Quando tutte le cose sopra non hanno funzionato, prova a dare ad Excel un po 'di tempo per chiudere i suoi fogli:

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

...
FinalReleaseComObject(app);

Assicurati di rilasciare tutti gli oggetti relativi a Excel!

Ho trascorso alcune ore provando diversi modi. Sono tutte idee fantastiche, ma alla fine ho trovato il mio errore: Se non rilasci tutti gli oggetti, nessuno dei modi sopra può aiutarti come nel mio caso. Assicurati di rilasciare tutti gli oggetti incluso l'intervallo uno!

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

Le opzioni sono insieme qui .

La regola dei due punti non ha funzionato per me. Nel mio caso ho creato un metodo per pulire le mie risorse come segue:

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

Dovresti stare molto attento usando le applicazioni di interoperabilità di Word / Excel. Dopo aver provato tutte le soluzioni avevamo ancora un sacco di & Quot; WinWord & Quot; processo lasciato aperto sul server (con oltre 2000 utenti).

Dopo aver lavorato sul problema per ore, mi sono reso conto che se avessi aperto più di un paio di documenti usando Word.ApplicationClass.Document.Open() contemporaneamente su thread diversi, il processo di lavoro IIS (w3wp.exe) si sarebbe arrestato in modo anomalo lasciando aperti tutti i processi WinWord!

Quindi suppongo non ci sia una soluzione assoluta a questo problema, ma il passaggio ad altri metodi come Office Open XML sviluppo.

Un ottimo articolo sul rilascio di oggetti COM è 2.5 Rilascio di oggetti COM (MSDN).

Il metodo che suggerirei è di annullare il tuo Excel.Interopificare i riferimenti se sono variabili non locali e quindi chiamare GC.Collect() e GC.WaitForPendingFinalizers() due volte. Le variabili Interop con ambito locale verranno gestite automaticamente.

Ciò elimina la necessità di mantenere un riferimento denominato per ogni oggetto COM.

Ecco un esempio tratto dall'articolo:

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

Queste parole sono tratte dall'articolo:

  

In quasi tutte le situazioni, l'annullamento del riferimento RCW e la forzatura di una garbage collection verranno ripuliti correttamente. Se chiami anche GC.WaitForPendingFinalizers, la garbage collection sarà deterministica quanto puoi farla. Cioè, sarai abbastanza sicuro esattamente quando l'oggetto sarà stato ripulito & # 8212; al ritorno dalla seconda chiamata a WaitForPendingFinalizers. In alternativa, è possibile utilizzare Marshal.ReleaseComObject. Tuttavia, tieni presente che è molto improbabile che tu debba mai usare questo metodo.

La mia soluzione

[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 risposta accettata non ha funzionato per me. Il seguente codice nel distruttore ha fatto il lavoro.

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

Attualmente sto lavorando all'automazione di Office e mi sono imbattuto in una soluzione per questo che funziona ogni volta per me. È semplice e non comporta l'uccisione di alcun processo.

Sembra che semplicemente eseguendo il ciclo continuo degli attuali processi attivi e in qualunque modo "accedendo" a un processo Excel aperto, qualsiasi istanza sospesa di Excel verrà rimossa. Il codice seguente controlla semplicemente i processi in cui il nome è 'Excel', quindi scrive la proprietà MainWindowTitle del processo in una stringa. Questa 'interazione' con il processo sembra fare in modo che Windows raggiunga e interrompa l'istanza congelata di Excel.

Eseguo il metodo seguente appena prima che il componente aggiuntivo che sto sviluppando venga chiuso, poiché genera l'evento di scaricamento. Rimuove ogni istanza sospesa di Excel ogni volta. In tutta onestà, non sono del tutto sicuro del perché funzioni, ma funziona bene per me e potrebbe essere posizionato alla fine di qualsiasi applicazione Excel senza doversi preoccupare di doppi punti, Marshal.ReleaseComObject, né di uccidere i processi. Sarei molto interessato a qualsiasi suggerimento sul perché questo sia 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;
                    }
                }
            }
}

Penso che in parte questo sia il modo in cui il framework gestisce le applicazioni di Office, ma potrei sbagliarmi. In alcuni giorni, alcune applicazioni puliscono immediatamente i processi e altri giorni sembrano attendere fino alla chiusura dell'applicazione. In generale, ho smesso di prestare attenzione ai dettagli e mi sono assicurato che alla fine della giornata non vi siano altri processi in sospeso.

Inoltre, e forse sto finendo di semplificare le cose, ma penso che tu possa semplicemente ...

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

Come ho detto prima, non tendo a prestare attenzione ai dettagli di quando appare o scompare il processo di Excel, ma di solito funziona per me. Inoltre, non mi piace mantenere i processi di Excel in giro per qualcosa di diverso dal minimo tempo, ma probabilmente sono solo paranoico su questo.

Come probabilmente alcuni hanno già scritto, non è solo importante il modo in cui chiudi Excel (oggetto); è anche importante il modo in cui apri e anche dal tipo di progetto.

In un'applicazione WPF, sostanzialmente lo stesso codice funziona senza o con pochissimi problemi.

Ho un progetto in cui lo stesso file Excel viene elaborato più volte per un valore di parametro diverso, ad es. analizzandolo in base ai valori all'interno di un elenco generico.

Ho inserito tutte le funzioni relative a Excel nella classe base e il parser in una sottoclasse (diversi parser usano le comuni funzioni di Excel). Non volevo che Excel fosse aperto e chiuso di nuovo per ogni elemento in un elenco generico, quindi l'ho aperto solo una volta nella classe base e lo ho chiuso nella sottoclasse. Ho avuto problemi durante lo spostamento del codice in un'applicazione desktop. Ho provato molte delle soluzioni sopra menzionate. GC.Collect() era già stato implementato prima, due volte come suggerito.

Quindi ho deciso di spostare il codice per l'apertura di Excel in una sottoclasse. Invece di aprirlo solo una volta, ora creo un nuovo oggetto (classe base) e apro Excel per ogni elemento e lo chiudo alla fine. C'è una certa penalità delle prestazioni, ma sulla base di numerosi test i processi di Excel si chiudono senza problemi (in modalità debug), quindi anche i file temporanei vengono rimossi. Continuerò con i test e scriverò ancora se avrò degli aggiornamenti.

La linea di fondo è: devi anche controllare il codice di inizializzazione, specialmente se hai molte classi, ecc.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top