2013-01-08 12 views
7

Sto creando un file excel utilizzando interop.excel e il processo non si sta chiudendo. Questo è il codice che sto cercando di usare.Il processo Excel non si chiude in VB.net

Private Sub converToExcel(fileLoc As String, ds As DataSet) 
    Dim xlApp As Excel.Application 
    Dim xlWorkBook As Excel.Workbook 
    Dim xlWorkBooks As Excel.Workbooks 
    Dim xlWorkSheet As Excel.Worksheet 
    Dim misValue As Object = System.Reflection.Missing.Value 
    Dim i As Integer 
    Dim j As Integer 

    xlApp = New Excel.Application 
    xlWorkBooks = xlApp.Workbooks 
    xlWorkBook = xlWorkBooks.Add(misValue) 
    xlWorkSheet = xlWorkBook.Sheets("sheet1") 

    For i = 0 To ds.Tables(0).Rows.Count - 1 
     For j = 0 To ds.Tables(0).Columns.Count - 1 
      xlWorkSheet.Columns.NumberFormat = "@" 
      xlWorkSheet.Cells(i + 1, j + 1) = String.Format("{0}", ds.Tables(0).Rows(i).Item(j).ToString()) 
     Next 
    Next 

    xlWorkSheet.SaveAs(fileLoc) 
    xlWorkBook.Close() 
    xlApp.Quit() 

    releaseObject(xlWorkSheet) 
    releaseObject(xlWorkBook) 
    releaseObject(xlWorkBooks) 
    releaseObject(xlApp) 

End Sub 
Private Sub releaseObject(ByVal obj As Object) 
    Try 
     System.Runtime.InteropServices.Marshal.ReleaseComObject(obj) 
     obj = Nothing 
    Catch ex As Exception 
     obj = Nothing 
    Finally 
     GC.Collect() 
    End Try 
End Sub 

Penso di mancare un oggetto COM ma non riesco a trovare una soluzione. Anche come nota, questo è in esecuzione su Windows 8 a 64 bit. Qualsiasi aiuto sarebbe fantastico! Grazie

+0

e si è sicuri che questo non è un caso di sinistro dalla sperimentazione precedente? – Fionnuala

+0

sì, controllo Task Manager dopo ogni test e rimuovo tutte le istanze di Excel. – jmcsmith

+0

Non penso che questa sia la causa del tuo problema, ma 'releaseObject' potrebbe non fare tutto quello che pensi che faccia. Poiché stai passando 'obj'' ByVal', 'obj = Nothing' non ha alcun effetto sulle variabili' xlWorkSheet', 'xlWorkBook' ecc. – prprcupofcoffee

risposta

1

Prova System.Runtime.InteropServices.Marshal.FinalReleaseComObject, che dovrebbe aiutare ... inoltre dovresti chiamare xlWorkBook.Close() e xlapp.quit, se ricordo male. Prima chiamali e poi non li fai nulla.

+0

Devo chiamare xlWorkBook.Close() e xlApp.Quit() prima di chiamare releaseObject. Provato usando finalRelease, stesso risultato. – jmcsmith

+0

Penso che l'ordine sia sbagliato con lo smettere: penso che si debbano impostare i seeh su null, quindi uscire dalla cartella di lavoro e quindi impostarlo su nulla, quindi quotare l'app e chiuderla. Ma ad ulteriore Investigazione ho visto che stai chiamando gc collect solo una volta - chiamalo due volte. In caso contrario, gli oggetti sono contrassegnati per il rilascio, ma non vengono rilasciati. –

1

Il GC.Collect non ha molto senso dove lo hai inserito, semmai dovresti chiamarlo dopo aver restituito da converToExcel. Potrebbe anche essere necessario attendere l'esecuzione dei finalizzatori. Personalmente penso che la risposta di Hans sia la strada da percorrere, ma so per esperienza personale scrivere addin in ufficio in C# che a volte è necessario fare il conteggio dei riferimenti manuali, in particolare quando è necessario essere compatibili con le versioni di office più datate. (Esistono molti problemi documentati, in particolare quando si gestiscono eventi dall'ufficio, che possono essere risolti in modo affidabile solo con il conteggio dei riferimenti manuale.Alcune librerie COM non sono affatto uguali quando vengono rilasciate nell'ordine sbagliato da GC, ma non è il caso con ufficio)

così via per il vero problema nel codice:. ci sono tre COM intermedio non oggetti rilasciati qui:

  • xlWorkBook.Sheets restituisce una raccolta di tipo Excel.Sheets
  • xlWorkSheet.Columns restituisce un oggetto COM di type Excel.Range
  • xlWorkSheet.Cells restituisce anche un oggetto Excel.Range

Oltre a questo, se Marshal.ReleaseComObject genera un'eccezione che hai fatto qualcosa di sbagliato nel vostro conteggio manuale di riferimento, quindi non vorrei avvolgerla in un gestore di eccezioni. Quando si esegue il conteggio del riferimento manuale, è necessario rilasciare ogni oggetto COM una volta ogni volta che attraversa il limite COM-> NET, ovvero gli oggetti devono essere rilasciati in ogni iterazione del ciclo.

Ecco il codice che termina correttamente Excel per me:

Imports Microsoft.Office.Interop 
Imports System.Runtime.InteropServices 

Private Sub converToExcel(fileLoc As String, ds As DataSet) 
    Dim xlApp As New Excel.Application 
    Dim xlWorkBooks As Excel.Workbooks = xlApp.Workbooks 
    Dim xlWorkBook As Excel.Workbook = xlWorkBooks.Add(System.Reflection.Missing.Value) 
    Dim xlWorkSheets As Excel.Sheets = xlWorkBook.Sheets 
    ' accessing the sheet by index because name is localized and your code will fail in non-english office versions 
    Dim xlWorkSheet As Excel.Worksheet = xlWorkSheets(1) 

    For i = 0 To ds.Tables(0).Rows.Count - 1 
     For j = 0 To ds.Tables(0).Columns.Count - 1 
      ' couldn't this be moved outside the loop? 
      Dim xlColumns As Excel.Range = xlWorkSheet.Columns 
      xlColumns.NumberFormat = "@" 
      Marshal.ReleaseComObject(xlColumns) 

      Dim xlCells As Excel.Range = xlWorkSheet.Cells 
      xlCells(i + 1, j + 1) = ds.Tables(0).Rows(i).Item(j).ToString() 
      Marshal.ReleaseComObject(xlCells) 
     Next 
    Next 

    xlWorkSheet.SaveAs(fileLoc) 
    'xlWorkBook.Close() -- not really necessary 
    xlApp.Quit() 

    Marshal.ReleaseComObject(xlWorkSheet) 
    Marshal.ReleaseComObject(xlWorkSheets) 
    Marshal.ReleaseComObject(xlWorkBook) 
    Marshal.ReleaseComObject(xlWorkBooks) 
    Marshal.ReleaseComObject(xlApp) 
End Sub 

Se si vuole essere molto attenti che ci si vuole gestire le eccezioni dalle API ufficio e chiamare ReleaseComObject all'interno infine-clausole. Può essere utile definire un wrapper generico e scrivere clausole using invece di try-finally (rendere il wrapper una struttura non una classe in modo da non allocare i wrapper nell'heap).

11

La gestione manuale della memoria come questa non funziona mai. Questo è un problema che è noto da molto tempo e il motivo principale per cui i netturbini sono stati inventati. I programmatori dimenticano per sempre di rilasciare memoria.

Diventa più difficile quando si visualizza la memoria utilizzata. Quale è certamente il caso nel tuo codice, l'espressione xlWorkSheet.Cells(i + 1, j + 1) utilizza non meno di tre riferimenti.Uno per l'oggetto intervallo restituito dalla proprietà Cells, uno per un oggetto sub-range selezionato da i+1 e un altro per l'oggetto sub-range selezionato da j+1. Molto bello lo zucchero di sintassi fornito dal linguaggio VB.NET, scrivere codice COM senza di esso è abbastanza doloroso. Ma non è utile per farti vedere i riferimenti. Non solo non riesci a vederlo nel tuo codice sorgente, non c'è assolutamente nulla che il debugger possa fare per aiutarti a vederli.

Questo è un problema molto risolto in .NET, ha un garbage collector e può vedere tutto. Il problema più semplice è che non gli dai la possibilità di risolvere il tuo problema. L'errore che hai fatto è che hai smesso di . Probabilmente impostando un punto di interruzione sull'ultima istruzione e poi guardando in Task Manager e vedendo Excel.exe ancora in esecuzione. Sì, è normale. La raccolta dei rifiuti non è istantanea.

Chiamare GC.Collect() dovrebbe renderlo istantaneo, ma ciò non funziona nel caso specifico dell'esecuzione della build Debug del progetto. La durata delle variabili locali viene quindi estesa alla fine del metodo, consentendoti di vederle nella finestra di Autos/Locals/Watch. In altre parole, GC.Collect() non raccoglie in effetti qualsiasi dei riferimenti dell'interfaccia. Ulteriori informazioni su tale comportamento in this post.

La soluzione semplice è non stop. Continua a fare cose utili per dare al garbage collector un motivo per correre. O lasciando che il programma si interrompa da quando è terminato, Excel termina quando il thread del finalizzatore viene eseguito per l'ultima volta. Che funziona perché le variabili locali che avevano i riferimenti non sono più in ambito.

Ma tutti vogliono la soluzione istantanea comunque. Si ottiene eliminando tutte le chiamate releaseObject(). E facendo in questo modo invece:

converToExcel(path, dset) 
GC.Collect() 
GC.WaitForPendingFinalizers() 

O in altre parole, la forza di una collezione dopo il metodo è tornato. Le variabili locali non sono più in ambito, quindi non possono trattenere un riferimento Excel. Ora funzionerà anche quando esegui il debug, come già fatto quando hai eseguito la build di Release senza un debugger.

+0

Mentre la soluzione è generalmente la strada da percorrere, non sempre funziona. Quando è necessario essere compatibili con le versioni di Office precedenti, esistono molti casi documentati in cui è necessario eseguire il conteggio dei riferimenti manuali, in particolare durante la scrittura di componenti aggiuntivi e la gestione degli eventi. Anche alcune librerie COM (di Microsoft, ma fortunatamente non di Office) non amano i loro oggetti rilasciati nell'ordine sbagliato da GC. – Zarat

+0

Questo è piuttosto claptrap molto diffuso. Comuni quando un piccolo cambiamento arbitrario ha grossi effetti collaterali, rendendo tutto simile alla magia nera. Questi "molti casi documentati" richiedono ovviamente una citazione. Ecco il mio: http://blogs.msdn.com/b/visualstudio/archive/2010/03/01/marshal-releasecomobject-considered-dangerous.aspx –

+0

Siamo spiacenti, non è possibile trovarli da ricerche di google veloci, ma quando Ho studiato questo argomento alcuni anni fa c'erano molte segnalazioni di utenti che non si chiudevano con addin gestiti (la causa più comune era la sottoscrizione agli eventi), o anche alcuni (molto rari) arresti dovuti a GC che rilasciava riferimenti a volte l'ufficio non poteva trattare con. Probabilmente tutti sono stati risolti nelle versioni moderne di Office (e .NET 4 è stato utile per l'iscrizione all'evento), quindi sono rilevanti solo se è necessario essere compatibili con le versioni precedenti. C'erano anche alcuni articoli su Microsoft KB, ma sono davvero difficili da trovare. – Zarat

-2
Dim xlp() As Process = Process.GetProcessesByName("EXCEL") 

For Each Process As Process In xlp 
    Process.Kill() 
    If Process.GetProcessesByName("EXCEL").Count = 0 Then 
     Exit For 
    End If 
Next 
+0

I processi di uccisione sono raramente la soluzione giusta, non consentono al target di eseguire una corretta pulizia. Per la domanda postata esistono soluzioni pulite, quindi non si dovrebbe mai ricorrere all'exec di uccisione. – Zarat

+0

A volte non pulisce il processo di Excel e mentre genera excel ripetutamente, crea più di 1 processo excel che dovrebbe rallentare il processo e il sistema. Questo era il problema che stavo usando e questa soluzione funzionava bene in il mio caso :) – Codebits

+3

Potrebbe aver funzionato nel tuo caso, ma non è utile suggerirlo come soluzione generica senza spiegarne le conseguenze. Ad esempio, ucciderebbe * qualsiasi * processo di Excel, compresi quelli che l'utente ha aperto. La maggior parte delle persone non vuole installare un programma solo per scoprire che ucciderà le loro istanze di Excel solo perché il programmatore non potrebbe pensare a un modo pulito per interagire con Excel. – Zarat

1

Infine risolto :)

Private Function useSomeExcel(ByVal Excelfilename As String) 
    Dim objExcel As Excel.Application 
    Dim objWorkBook As Excel.Workbook 
    Dim objWorkSheets As Excel.Worksheet 

    Dim datestart As Date = Date.Now 
    objExcel = CreateObject("Excel.Application") 'This opens... 
    objWorkBook = objExcel.Workbooks.Open(Excelfilename) ' ... excel process 
    Dim dateEnd As Date = Date.Now 
    End_Excel_App(datestart, dateEnd) ' This closes excel proces 
End Function 

uso questo metodo

Private Sub End_Excel_App(datestart As Date, dateEnd As Date) 
    Dim xlp() As Process = Process.GetProcessesByName("EXCEL") 
    For Each Process As Process In xlp 
    If Process.StartTime >= datestart And Process.StartTime <= dateEnd Then 
     Process.Kill() 
     Exit For 
    End If 
    Next 
    End Sub 

Questo metodo chiude processo especific aperto.

0
'Get the PID from the wHnd and kill the process. 
' open the spreadsheet 
ImportFileName = OpenFileDialog1.FileName 
excel = New Microsoft.Office.Interop.Excel.ApplicationClass 
wBook = excel.Workbooks.Open(ImportFileName) 
hWnd = excel.Hwnd 
Dim id As Integer = GetWindowThreadProcessId(hWnd, ExcelPID) 

Sub CloseExcelFile() 
     Try 
      ' first try this 
      wBook.Saved = True 
      wBook.Close() 
      excel.Quit() 

      ' then this. 
      System.Runtime.InteropServices.Marshal.ReleaseComObject(excel) 
      excel = Nothing 

      ' This appears to be the only way to close excel! 
      Dim oProcess As Process 
      oProcess = Process.GetProcessById(ExcelPID) 
      If oProcess IsNot Nothing Then 
       oProcess.Kill() 
      End If 

     Catch ex As Exception 
      excel = Nothing 
     Finally 
      GC.Collect() 
     End Try 
    End Sub 
+0

Benvenuti in Stack Overflow! Sebbene questo codice possa rispondere alla domanda, sarebbe meglio includere qualche _context_, spiegando _come_ funziona e _quando_ per usarlo. Le risposte al solo codice non sono utili a lungo termine. –

Problemi correlati