Excel Prozess nach Schreiben in Exceldatei beenden

  • VB.NET

Es gibt 9 Antworten in diesem Thema. Der letzte Beitrag () ist von Stangl1.

    Excel Prozess nach Schreiben in Exceldatei beenden

    Hallo,
    mit folgendem Code schreibe ich Daten in eine Exceldatei:

    VB.NET-Quellcode

    1. Dim filePath As String = "Test.xlsm"
    2. Dim xlApp As New Excel.Application ' Excel App-Instanz
    3. Dim xlWorkbook As Excel.Workbook
    4. xlApp.Visible = False
    5. xlApp.EnableEvents = False
    6. Try
    7. xlWorkbook = xlApp.Workbooks.Open(filePath) ' Excel-Mappe öffnen
    8. Catch ex As Exception
    9. Console.WriteLine(vbNewLine & ex.Message)
    10. Exit Sub
    11. End Try
    12. Dim xlSheet As Excel.Worksheet = xlWorkbook.Worksheets(1)
    13. With xlSheet
    14. 'Daten in die .Value Eigenschaft der Zellen schreiben
    15. End With
    16. xlWorkbook.Close(True)
    17. xlApp.EnableEvents = True
    18. xlApp.Quit()
    19. xlApp = Nothing
    20. xlWorkbook = Nothing
    21. xlSheet = Nothing

    Danach habe ich im Taskmanager pro Ausführung des Codes einen Prozess EXCEL.EXE *32 .
    Ich habe im Internet die drei letzten Zeilen gefunden, die das Problem jedoch nicht lösen.
    Welche Anweisung brauch ich noch?

    Gruß Ludwig
    Ich habs mal korrigiert:

    VB.NET-Quellcode

    1. Dim filePath As String = "Test.xls"
    2. Dim xlApp As New Excel.Application ' Excel App-Instanz
    3. Dim xlWorkbook As Excel.Workbook
    4. Dim xlWorkbooks As Excel.Workbooks = xlApp.Workbooks
    5. xlApp.Visible = False
    6. xlApp.EnableEvents = False
    7. Try
    8. xlWorkbook = xlWorkbooks.Open(filePath) ' Excel-Mappe öffnen
    9. Catch ex As Exception
    10. Console.WriteLine(vbNewLine & ex.Message)
    11. Exit Sub
    12. End Try
    13. Dim xlSheets As Excel.Sheets = xlWorkbook.Worksheets
    14. Dim xlSheet As Excel.Worksheet = DirectCast(xlSheets.Item(1), Excel.Worksheet)
    15. With xlSheet
    16. 'Daten in die .Value Eigenschaft der Zellen schreiben
    17. End With
    18. Marshal.ReleaseComObject(xlSheet)
    19. Marshal.ReleaseComObject(xlSheets)
    20. xlWorkbook.Close(True)
    21. Marshal.ReleaseComObject(xlWorkbook)
    22. Marshal.ReleaseComObject(xlWorkbooks)
    23. xlApp.EnableEvents = True
    24. xlApp.Quit()
    25. Marshal.ReleaseComObject(xlApp)

    Wenn du Objekte anforderst, musst du sie natürlich auch freigeben. Das liegt am Programmiermodell von Excel - es basiert auf COM. Dort muss alles explizit freigegeben werden, anders als in .NET. Wichtig ist dabei, dass du keine Referenzen erzeugst, die du nicht mehr freigeben kannst (z.B. Excel.Worksheets(1).Range(bla).SetValue(xyz) - jeder Teilaufruf erzeugt ein Objekt, das später den Excel-Prozess vom Beenden abhält, weil es nicht freigegeben wurde.

    Es kann sein, dass der Excel-Prozess auch mit dem korrigierten Code nicht direkt endet. Er ist trotzdem richtig, denn .NET hat seinen eigenen Willen, was die Freigabe von Objekten angeht (Stichwort Garbage Collection). Da hilft auch kein Marshal.ReleaseComObject() - aber dadurch wird es zumindest zum Abschuss freigegeben. Der GC sammelt es dann irgendwann ein, und dann endet auch der Excel-Prozess.
    Gruß
    hal2000
    Hallo hal2000,
    Vielen Dank für deine Hilfe!
    Marshal gibt bei mir den Fehler "Marshal wurde nicht deklariert". Muss ich noch irgendwas importieren?
    Wichtig ist dabei, dass du keine Referenzen erzeugst, die du nicht mehr
    freigeben kannst (z.B. Excel.Worksheets(1).Range(bla).SetValue(xyz) -
    jeder Teilaufruf erzeugt ein Objekt, das später den Excel-Prozess vom
    Beenden abhält, weil es nicht freigegeben wurde.
    Heißt das, dass

    VB.NET-Quellcode

    1. .cells(zeile,3).value = Variable1 / Variable2 * 100
    ebenfalls ein Objekt erzeugen würde, das das Beenden des Prozesses verhindern würde?


    Gruß Ludwig

    LudwigM schrieb:

    Hallo hal2000,
    Vielen Dank für deine Hilfe!
    Marshal gibt bei mir den Fehler "Marshal wurde nicht deklariert". Muss ich noch irgendwas importieren?

    VB.NET-Quellcode

    1. System.Runtime.InteropServices.Marshal.ReleaseComObject(<OBJEKT>)


    Klappt bei mir aber trotzdem nicht, ich suche schon seit einem Jahr nach einer funktionierenden Lösung.
    Mein
    Workaround ist es eine Liste der Excelprozesse unmittelbar vor meiner
    eigenen Routine zu erzeugen und nach der Routine wieder eine neue
    Prozessliste.
    Alle Excelprozesse die jetzt noch zusätzlich vorhanden sind, kill ich dann.

    Es funktioniert und gab bisher nie Fehler, aber wohler wäre mir mit einer besseren Lösung.

    hth,
    Haro

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „HaRoWagner“ ()

    Vielen Dank xtts02,
    aber es funktioniert noch nicht, der Excel Prozess bleibt erhalten.

    Marshal.ReleaseComObject(xlWorkbook)

    Marshal.ReleaseComObject(xlWorkbooks)
    Wofür ist die zweite Zeile, sie funktionniert nicht - nicht deklariert!

    Gruß Ludwig

    LudwigM schrieb:

    Wofür ist die zweite Zeile, sie funktionniert nicht - nicht deklariert!
    Doch, wurde sie (in Zeile 4). Die Variable in Zeile 3 wird erst später verwendet.

    LudwigM schrieb:

    Marshal gibt bei mir den Fehler "Marshal wurde nicht deklariert". Muss ich noch irgendwas importieren?
    Das hatte ich vergessen: Oben in der Datei muss Imports System.Runtime.InteropServices stehen, wie xtts schon sagte.

    LudwigM schrieb:

    Heißt das, dass [...] ebenfalls ein Objekt erzeugen würde, das das Beenden des Prozesses verhindern würde?
    Ja. Richtig wäre in diesem Fall:

    VB.NET-Quellcode

    1. Dim cell As Excel.Range = DirectCast([Objekt].Cells(zeile,3), Excel.Range)
    2. cell.Value = Variable1 / Variable2 * 100
    3. Marshal.ReleaseComObject(cellRange)

    "Cells" liefert ein Range-Objekt (!) zurück, das in deinem Aufruf verloren geht.

    @HaRoWagner:: Wichtig ist, dass dir keine Referenzen zwischendurch verloren gehen. Die zu finden kann sehr mühsam sein.

    Ich habe mal einen "Beweis" geschrieben, der meine Aussage von oben bestätigt, dass der Excel-Prozess beendet wird, wenn der GC die zum Abschuss freigegebenen Objekte eingesammelt hat. Das tut er, wenn es erforderlich wird, d.h. wenn nicht mehr genügend Speicher vorhanden ist. Wir beweisen das, indem wir wahllos Speichern anfordern (Beispielanwendung mit 1x TextBox, 2 Buttons und folgendem Code):

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.Runtime.InteropServices
    2. Imports Microsoft.Office.Interop
    3. Public Class Form1
    4. Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
    5. Dim filePath As String = "Test.xls"
    6. Dim xlApp As New Excel.Application ' Excel App-Instanz
    7. Dim xlWorkbook As Excel.Workbook
    8. Dim xlWorkbooks As Excel.Workbooks = xlApp.Workbooks
    9. xlApp.Visible = False
    10. xlApp.EnableEvents = False
    11. Try
    12. xlWorkbook = xlWorkbooks.Open(filePath) ' Excel-Mappe öffnen
    13. Catch ex As Exception
    14. MessageBox.Show(ex.Message)
    15. 'Das hat auch noch gefehlt - Freigabe der Excel-Application im Fehlerfall.
    16. Marshal.ReleaseComObject(xlWorkbooks)
    17. xlApp.EnableEvents = True
    18. xlApp.Quit()
    19. Marshal.ReleaseComObject(xlApp)
    20. Exit Sub
    21. End Try
    22. Dim xlSheets As Excel.Sheets = xlWorkbook.Worksheets
    23. Dim xlSheet As Excel.Worksheet = DirectCast(xlSheets.Item(1), Excel.Worksheet)
    24. With xlSheet
    25. Dim r As Excel.Range = DirectCast(xlSheet.Cells(1, 1), Excel.Range)
    26. r.Value = TextBox1.Text
    27. Marshal.ReleaseComObject(r)
    28. 'Daten in die .Value Eigenschaft der Zellen schreiben
    29. End With
    30. Marshal.ReleaseComObject(xlSheet)
    31. Marshal.ReleaseComObject(xlSheets)
    32. xlWorkbook.Close(True)
    33. xlWorkbooks.Close()
    34. Marshal.ReleaseComObject(xlWorkbook)
    35. Marshal.ReleaseComObject(xlWorkbooks)
    36. xlApp.EnableEvents = True
    37. xlApp.Quit()
    38. Marshal.ReleaseComObject(xlApp)
    39. End Sub
    40. Dim lst As New List(Of MemoryGarbage)
    41. Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
    42. MessageBox.Show("Creating memory garbage...")
    43. For i = 1 To 10000
    44. lst.Add(New MemoryGarbage())
    45. Next
    46. MessageBox.Show("Excel process should have exited now.")
    47. lst.Clear()
    48. End Sub
    49. End Class
    50. Class MemoryGarbage
    51. Dim myArray(10000) As Int32
    52. End Class

    Button1 erzeugt einen Excel-Prozess, der sich scheinbar nicht beendet, und schreibt den Inhalt der TextBox in Zelle (1,1). Das passiert aber prompt, wenn Button2 angeklickt und die beiden Meldungen bestätigt wurden. Das bedeutet im Umkehrschluss, dass alle Objekte korrekt freigegeben wurden.
    Gruß
    hal2000
    Hallo Hal2000,

    ich kann mich nur bedanken für Deine super Ausführung.

    Ich habe es auch gleich zum Anlass genommen eine meiner Module auf nicht ordentlich freigegebene Objekte hin zu untersuchen.
    Natürlich bin ich fündig geworden, es waren auch bei mir "mal eben so" dahin deklarierte Bereiche gewesen, die nicht sauber oder gar nicht "released" worden sind.

    Auch ohne mein Workaround ist Excel direkt wieder aus der Prozessliste verschwunden.

    Einfach Super,
    Danke Dir.

    Haro
    Wenns XLSX sein darf: epplus.codeplex.com/

    Mit der Library kannst du Excel Dokumente über das Open Office Xml format erstellen, welches problemlos ab Office 2007 gelesen werden kann. Hier wird komplett auf die Excel Instanz verzichtet (und performanter ist das Ganze auch. :)

    Bsp.: Alle Zellen von A1 bis K10001 mit Zahlen befüllen

    VB.NET-Quellcode

    1. Imports System.Runtime.InteropServices
    2. Imports OfficeOpenXml
    3. Public Class Class1
    4. Sub Test()
    5. 'Hier wird die Datei definiert, die erstellt bzw. geöffnet werden soll
    6. Dim ExcelFile As New System.IO.FileInfo("C:\Users\steve\Desktop\dll\sample6.xlsx")
    7. 'Falls die Datei bereits existiert, wird diese gelöscht
    8. If ExcelFile.Exists Then ExcelFile.Delete()
    9. 'Hier werden ein paar Zellen befüllt
    10. Using Excel As New ExcelPackage(ExcelFile)
    11. Dim ws As ExcelWorksheet = Excel.Workbook.Worksheets.Add("Content")
    12. Using start As ExcelRange = ws.Cells("A1")
    13. Dim counter As Integer = 0
    14. For i As Integer = 0 To 10000
    15. For y As Integer = 0 To 10
    16. counter += 1
    17. start.Offset(i, y).Value = counter
    18. Next
    19. Next
    20. End Using
    21. Excel.Save()
    22. End Using
    23. End Sub
    24. End Class
    SWYgeW91IGNhbiByZWFkIHRoaXMsIHlvdSdyZSBhIGdlZWsgOkQ=

    Weil einfach, einfach zu einfach ist! :D

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „BiedermannS“ ()

    Hallo zusammen,
    das hier ist zwar schon alt, hat mir aber sehr geholfen.
    Genau diese Prozesse haben mich auch gestört, hatte davon im Task-Manager manchmal 20 Stück.

    Die Performance von EPPlus ist wirklich nicht zu verachten.

    Hier mein Code, läuft für mich perfekt, evtl. hilft das hier auch wieder jemandem weiter:

    VB.NET-Quellcode

    1. Imports System.IO
    2. Imports OfficeOpenXml
    3. Imports OfficeOpenXml.Style
    4. Private Sub Art_schreiben(ByVal aMessage As Boolean)
    5. If DataGridViewMyArt.ColumnCount < 1 Then
    6. MyMessageBox("Hinweis", "Keine Daten vorhanden!")
    7. Exit Sub
    8. End If
    9. 'Hier wird die Datei definiert, die erstellt bzw. geöffnet werden soll
    10. Dim ExcelFile As New System.IO.FileInfo(MyArt_PATH.Text)
    11. 'Falls die Datei existiert, wird diese gesichert dann gelöscht
    12. If ExcelFile.Exists Then
    13. File.Copy(ExcelFile.ToString, ExcelFile.ToString & "_bak", True)
    14. ExcelFile.Delete()
    15. End If
    16. 'Grid sortieren
    17. Call Sort_MyGrid(DataGridViewMyArt, "AZ")
    18. 'Hier werden die Zellen befüllt
    19. Using Excel As New ExcelPackage(ExcelFile)
    20. Dim ws As ExcelWorksheet = Excel.Workbook.Worksheets.Add("Tabelle1")
    21. 'Spalten definieren
    22. ws.Column(1).Width = 20
    23. ws.Column(2).Width = 30
    24. ws.Column(3).Width = 10
    25. Using eRange As ExcelRange = ws.Cells("A1")
    26. 'Erste Zeile schreiben, Start bei A1
    27. For oSpalte As Integer = 0 To DataGridViewMyArt.ColumnCount - 1
    28. eRange.Offset(0, oSpalte).Value = DataGridViewMyArt.Columns.Item(oSpalte).HeaderText
    29. eRange.Offset(0, oSpalte).Style.Fill.PatternType = ExcelFillStyle.Solid
    30. eRange.Offset(0, oSpalte).Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.Orange)
    31. eRange.Offset(0, oSpalte).Style.Font.Size = 14
    32. Next
    33. End Using
    34. 'Zeilen schreiben, Start bei A2
    35. Using eRange As ExcelRange = ws.Cells("A2")
    36. For oZeile As Integer = 0 To DataGridViewMyArt.RowCount - 1
    37. For oSpalte As Integer = 0 To DataGridViewMyArt.ColumnCount - 1
    38. eRange.Offset(oZeile, oSpalte).Value = DataGridViewMyArt.Rows(oZeile).Cells(oSpalte).Value
    39. eRange.Offset(oZeile, oSpalte).Style.Font.Size = 12
    40. Next
    41. Next
    42. End Using
    43. Excel.Save()
    44. Excel.Dispose()
    45. End Using
    46. 'Grid sortieren
    47. Call Sort_MyGrid(DataGridViewMyArt, "ZA")
    48. If aMessage Then MyMessageBox("Hinweis", "Fertig!")
    49. End Sub
    50. Private Sub Art_einlesen()
    51. 'Grid leeren
    52. DataGridViewMyArt.Columns.Clear()
    53. DataGridViewMyArt.Rows.Clear()
    54. 'Hier wird die Datei definiert, die erstellt bzw. geöffnet werden soll
    55. Dim ExcelFile As New System.IO.FileInfo(MyArt_PATH.Text)
    56. Using Excel As New ExcelPackage(ExcelFile)
    57. Dim ws As ExcelWorksheet = Excel.Workbook.Worksheets.First()
    58. 'Inhalt vorab prüfen
    59. Try
    60. If ws.Dimension.End.Row <= 1 Then
    61. MyMessageBox("Hinweis", "Datei prüfen, keine Zeilen gefunden!")
    62. Exit Sub
    63. End If
    64. Catch ex As Exception
    65. MyMessageBox("Hinweis", "Datei prüfen, keine Zeilen gefunden!")
    66. Exit Sub
    67. End Try
    68. 'Header schreiben, Excel 1. Zeile
    69. For col As Integer = 1 To ws.Dimension.End.Column
    70. 'DataGridView beginnt bei 0 -> col - 1
    71. DataGridViewMyArt.Columns.Add(col - 1, ws.Cells(1, col).Value)
    72. Next
    73. 'Zeilen schreiben, ab Excel 2. Zeile
    74. For row As Integer = 2 To ws.Dimension.End.Row
    75. DataGridViewMyArt.Rows.Add()
    76. For col As Integer = 1 To ws.Dimension.End.Column
    77. 'DataGridView beginnt bei 0 -> row - 2, col - 1
    78. DataGridViewMyArt.Rows(row - 2).Cells(col - 1).Value = ws.Cells(row, col).Value
    79. Next
    80. Next
    81. Excel.Save()
    82. Excel.Dispose()
    83. End Using
    84. 'Spaltenbreiten anpassen
    85. DataGridViewMyArt.Columns(0).Width = 120
    86. DataGridViewMyArt.Columns(1).Width = 337
    87. DataGridViewMyArt.Columns(2).Width = 80
    88. For i As Integer = 0 To 1
    89. DataGridViewMyArt.Columns.Item(i).ReadOnly = True
    90. Next
    91. DataGridViewMyArt.AllowUserToAddRows = False
    92. DataGridViewMyArt.AllowUserToDeleteRows = True
    93. 'Grid sortieren
    94. Call Sort_MyGrid(DataGridViewMyArt, "ZA")
    95. Row_Cont.Text = ""
    96. Row_Cont.Text = DataGridViewMyArt.RowCount & " Zeilen"
    97. MyMessageBox("Hinweis", "Fertig!")
    98. End Sub

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „Stangl1“ ()