ฉันจะทำความสะอาดวัตถุ interop ของ Excel อย่างเหมาะสมได้อย่างไร


747

ฉันใช้การทำงานร่วมกันของ Excel ใน C # ( ApplicationClass) และได้วางรหัสต่อไปนี้ในประโยคสุดท้ายของฉัน:

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

แม้ว่างานประเภทนี้Excel.exeจะยังอยู่ในพื้นหลังแม้ว่าฉันจะปิด Excel ก็ตาม มันจะเปิดตัวก็ต่อเมื่อแอปพลิเคชันของฉันถูกปิดด้วยตนเอง

ฉันกำลังทำอะไรผิดหรือมีทางเลือกอื่นเพื่อให้แน่ใจว่ามีการกำจัดวัตถุ interop อย่างถูกต้องหรือไม่


1
คุณกำลังพยายามปิด Excel.exe โดยไม่ปิดแอปพลิเคชันของคุณหรือไม่ ไม่แน่ใจว่าฉันเข้าใจคำถามของคุณอย่างเต็มที่
ไบรอันท์

3
ฉันพยายามทำให้แน่ใจว่ามีการกำจัดวัตถุ interop ที่ไม่มีการจัดการอย่างเหมาะสม เพื่อไม่ให้มีกระบวนการ Excel ค้างอยู่แม้ว่าผู้ใช้เสร็จสิ้นด้วยสเปรดชีต Excel ที่เราสร้างขึ้นจากแอป
HAdes

3
หากคุณสามารถลองทำได้โดยการสร้างไฟล์ XML Excel มิฉะนั้นโปรดพิจารณา VSTO un / จัดการหน่วยความจำที่ได้รับการจัดการ: jake.ginnivan.net/vsto-com-interop
Jeremy Thompson

สิ่งนี้แปลเป็น Excel ได้ดีหรือไม่
Coops

2
ดู (นอกเหนือจากคำตอบด้านล่าง) บทความสนับสนุนนี้จาก Microsoft ที่พวกเขาให้การแก้ปัญหานี้เป็นพิเศษ: support.microsoft.com/kb/317109
Arjan

คำตอบ:


684

Excel ไม่ออกเนื่องจากแอปพลิเคชันของคุณยังคงมีการอ้างอิงถึงวัตถุ COM

ฉันเดาว่าคุณเรียกใช้สมาชิก COM วัตถุอย่างน้อยหนึ่งคนโดยไม่ได้กำหนดให้กับตัวแปร

สำหรับฉันมันเป็นวัตถุexcelApp.Worksheetsซึ่งฉันใช้โดยตรงโดยไม่ต้องกำหนดให้กับตัวแปร:

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

ฉันไม่ทราบว่า C # ภายในสร้าง wrapper สำหรับวัตถุ COM ของWorksheetsซึ่งไม่ได้รับการเผยแพร่โดยรหัสของฉัน (เพราะฉันไม่ทราบ) และเป็นสาเหตุที่ทำให้ Excel ไม่ถูกยกเลิกการโหลด

ฉันพบวิธีแก้ไขปัญหาของฉันในหน้านี้ซึ่งมีกฎที่ดีสำหรับการใช้งานวัตถุ COM ใน C #:

อย่าใช้จุดสองจุดกับวัตถุ COM


ดังนั้นด้วยความรู้นี้วิธีที่ถูกต้องในการทำข้างต้นคือ:

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

การอัปเดตการโพสต์ MORTEM:

ฉันต้องการให้ผู้อ่านทุกคนอ่านคำตอบของฮันส์แพสแทนท์อย่างระมัดระวังเพราะอธิบายกับดักที่ฉันและผู้พัฒนารายอื่นจำนวนมากสะดุด เมื่อฉันเขียนคำตอบนี้เมื่อหลายปีก่อนฉันไม่รู้เกี่ยวกับผลกระทบที่ตัวดีบักมีต่อตัวเก็บขยะและดึงข้อสรุปที่ผิด ฉันเก็บคำตอบไว้โดยไม่เปลี่ยนแปลงเพื่อประโยชน์ของประวัติศาสตร์ แต่โปรดอ่านลิงก์นี้และอย่าไปตาม "จุดสองจุด": ทำความเข้าใจกับการรวบรวมขยะใน. NETและทำความสะอาด Excel Interop Objects ด้วย IDisposable


16
ถ้าอย่างนั้นฉันไม่แนะนำให้ใช้ Excel จาก COM และช่วยตัวคุณเองให้ทุกปัญหา สามารถใช้รูปแบบ Excel 2007 ได้โดยไม่ต้องเปิด Excel เลย
user7116

5
ฉันไม่เข้าใจความหมายของ "สองจุด" คุณช่วยอธิบายได้มั้ย
A9S6

22
หมายความว่าคุณไม่ควรใช้รูปแบบ comObject.Property.PropertiesProperty (คุณเห็นจุดสองจุดหรือไม่) แทนที่จะกำหนด comObject.Property ให้กับตัวแปรและใช้และกำจัดตัวแปรนั้น รุ่นที่เป็นทางการมากขึ้นของกฎข้างต้นอาจเป็นฏ เช่น "Assign com object ให้กับตัวแปรก่อนที่คุณจะใช้ซึ่งรวมถึงวัตถุ com ที่เป็นคุณสมบัติของวัตถุ com อื่น"
VVS

5
@Nick: จริง ๆ แล้วคุณไม่จำเป็นต้องล้างข้อมูลใด ๆ เนื่องจากตัวรวบรวมขยะจะทำเพื่อคุณ สิ่งเดียวที่คุณต้องทำคือการกำหนดวัตถุ COM ทุกตัวให้กับตัวแปรของตัวเองเพื่อให้ GC ทราบถึงสิ่งนั้น
VVS

12
@VSS นั่นคือ bollocks GC จะล้างข้อมูลทุกอย่างเนื่องจากตัวแปร wrapper ทำโดย. net framework อาจใช้เวลาตลอดไปเพื่อให้ GC ทำความสะอาด การเรียก GC.Collect หลังจากการทำงานหนักระหว่างกันไม่ได้เลวร้ายนัก
CodingBarfield

280

คุณสามารถปล่อยวัตถุแอปพลิเคชัน Excel ของคุณได้อย่างหมดจด แต่คุณต้องระวัง

คำแนะนำในการรักษาชื่ออ้างอิงสำหรับวัตถุ COM ทุกรายการที่คุณเข้าถึงอย่างชัดเจนแล้วปล่อยผ่านอย่างชัดเจนนั้นMarshal.FinalReleaseComObject()ถูกต้องในทางทฤษฎี แต่น่าเสียดายที่ยากมากในการจัดการในทางปฏิบัติ หากมีใครลื่นหลุดไปไหนและใช้ "จุดสองจุด" หรือวนซ้ำเซลล์ผ่านfor eachลูปหรือคำสั่งอื่น ๆ ที่คล้ายกันคุณจะมีวัตถุ COM ที่ไม่มีการอ้างอิงและเสี่ยงต่อการแฮงค์ ในกรณีนี้จะไม่มีวิธีการค้นหาสาเหตุในรหัส คุณจะต้องตรวจสอบรหัสทั้งหมดของคุณด้วยตาและหวังว่าจะหาสาเหตุซึ่งเป็นงานที่แทบจะเป็นไปไม่ได้สำหรับโครงการขนาดใหญ่

ข่าวดีก็คือว่าคุณไม่จำเป็นต้องเก็บรักษาตัวแปรอ้างอิงที่ตั้งชื่อไว้กับวัตถุ COM ทั้งหมดที่คุณใช้ ให้เรียกใช้GC.Collect()แล้วGC.WaitForPendingFinalizers()ปล่อยวัตถุ (ปกติเล็กน้อย) ทั้งหมดที่คุณไม่ได้เก็บการอ้างอิงจากนั้นปล่อยวัตถุที่คุณถือการอ้างอิงตัวแปรที่มีชื่ออย่างชัดเจน

คุณควรปล่อยการอ้างอิงที่มีชื่อของคุณในลำดับความสำคัญย้อนกลับ: ช่วงวัตถุก่อนจากนั้นจึงแผ่นงานสมุดงานและจากนั้นในที่สุดวัตถุ Excel Application ของคุณ

ตัวอย่างเช่นสมมติว่าคุณมีตัวแปรชื่อวัตถุเป็นช่วงชื่อตัวแปรxlRngเวิร์กชีทชื่อตัวแปรเวิxlSheetร์กบุคชื่อxlBookและตัวแปรแอปพลิเคชัน Excel ที่มีชื่อxlAppแล้วรหัสการล้างข้อมูลของคุณอาจมีลักษณะดังนี้:

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

ในตัวอย่างโค้ดส่วนใหญ่คุณจะเห็นการล้างค่าวัตถุ COM จาก. NET GC.Collect()และการGC.WaitForPendingFinalizers()โทรจะทำสองครั้งใน:

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

อย่างไรก็ตามไม่ควรใช้สิ่งนี้เว้นแต่ว่าคุณกำลังใช้ Visual Studio Tools สำหรับ Office (VSTO) ซึ่งใช้ finalizers ซึ่งทำให้กราฟวัตถุทั้งหมดได้รับการเลื่อนตำแหน่งในคิวการสรุป วัตถุดังกล่าวจะไม่ถูกปล่อยออกมาจนกว่าจะมีการเก็บขยะครั้งต่อไป อย่างไรก็ตามหากคุณไม่ได้ใช้ VSTO คุณควรจะสามารถโทรGC.Collect()และใช้งานได้GC.WaitForPendingFinalizers()เพียงครั้งเดียว

ฉันรู้ว่าการโทรอย่างชัดเจนGC.Collect()คือไม่ห้าม (และแน่นอนว่าการโทรสองครั้งจะฟังดูเจ็บปวดมาก) แต่ไม่มีทางที่จะซื่อสัตย์ GC.Collect()ผ่านการดำเนินงานปกติคุณจะสร้างวัตถุที่ซ่อนอยู่ที่คุณถือไม่มีการอ้างอิงว่าคุณจึงไม่สามารถปล่อยผ่านวิธีการอื่นใดนอกเหนือจากการโทร

นี่เป็นหัวข้อที่ซับซ้อน แต่จริงๆแล้วมันมีอยู่ทั้งหมด เมื่อคุณสร้างแม่แบบนี้สำหรับขั้นตอนการล้างข้อมูลของคุณคุณสามารถเขียนโค้ดได้ตามปกติโดยไม่ต้องใช้แผ่นปิด ฯลฯ :-)

ฉันมีการสอนเกี่ยวกับเรื่องนี้ที่นี่:

การทำให้โปรแกรม Office เป็นอัตโนมัติด้วย VB.Net / COM Interop

มันเขียนขึ้นสำหรับ VB.NET แต่อย่าเพิ่งถูกลบล้างโดยหลักการนั้นเหมือนกับเมื่อใช้ C #


3
การอภิปรายที่เกี่ยวข้องสามารถพบได้ในฟอรั่ม ExtremeVBTalk .NET Office Automation, ที่นี่: xtremevbtalk.com/showthread.php?t=303928
Mike Rosenblum

2
และหากทุกอย่างอื่นล้มเหลวแล้ว Process.Kill () สามารถใช้ (เป็นทางเลือกสุดท้าย) ตามที่อธิบายไว้ที่นี่: stackoverflow.com/questions/51462/ …
Mike Rosenblum

1
ดีใจที่ได้ทำงานกับริชาร์ด :-) และนี่คือตัวอย่างที่ละเอียดอ่อนที่เพียงแค่หลีกเลี่ยง "สองจุด" ไม่เพียงพอเพื่อป้องกันปัญหา: stackoverflow.com/questions/4964663//
Mike Rosenblum

นี่คือความเห็นเกี่ยวกับเรื่องนี้จาก Misha Shneerson ของ Microsoft ในหัวข้อภายในวันที่ความคิดเห็น 7 ตุลาคม 2010 ( blogs.msdn.com/b/csharpfaq/archive/2010/09/28/ … )
Mike Rosenblum

ฉันหวังว่านี่จะช่วยแก้ปัญหาที่เรามีอยู่อย่างถาวร ปลอดภัยไหมที่จะทำการรวบรวมขยะและปล่อยสายภายในบล็อกสุดท้าย?
Brett Green

213

คำนำ: คำตอบของฉันมีสองวิธีดังนั้นระวังเมื่ออ่านและไม่พลาดอะไร

มีวิธีและคำแนะนำต่าง ๆ ในการทำให้การโหลดอินสแตนซ์ของ Excel เช่น:

  • ปล่อยวัตถุ com ทุกอย่างอย่างชัดเจนด้วย Marshal.FinalReleaseComObject () (ไม่ลืมเกี่ยวกับ com- วัตถุที่สร้างขึ้นโดยปริยาย) ในการปล่อยวัตถุ com ที่สร้างขึ้นทั้งหมดคุณสามารถใช้กฎของจุด 2 จุดที่กล่าวถึงที่นี่:
    ฉันจะล้างวัตถุ Excel interop อย่างถูกต้องได้อย่างไร

  • เรียก GC.Collect () และ GC.WaitForPendingFinalizers () เพื่อทำให้ CLR ปล่อย com-objects ที่ไม่ได้ใช้ * (จริง ๆ แล้วมันใช้งานได้ดูรายละเอียดวิธีที่สองของฉัน)

  • ตรวจสอบว่าแอปพลิเคชันเซิร์ฟเวอร์เซิร์ฟเวอร์อาจแสดงกล่องข้อความรอให้ผู้ใช้ตอบ (แม้ว่าฉันไม่แน่ใจว่าสามารถป้องกัน Excel จากการปิด แต่ฉันได้ยินเกี่ยวกับมันสองสามครั้ง)

  • ส่งข้อความ WM_CLOSE ไปที่หน้าต่างหลักของ Excel

  • การใช้งานฟังก์ชันที่ทำงานกับ Excel ใน AppDomain แยกต่างหาก บางคนเชื่อว่าอินสแตนซ์ของ Excel จะถูกปิดเมื่อไม่มีการโหลด AppDomain

  • ฆ่าอินสแตนซ์ excel ทั้งหมดซึ่งถูกสร้างอินสแตนซ์หลังจากโค้ด excel-interoping เริ่ม

แต่! บางครั้งตัวเลือกทั้งหมดเหล่านี้ก็ไม่ช่วยหรือไม่เหมาะสม!

ตัวอย่างเช่นเมื่อวานฉันพบว่าในหนึ่งในฟังก์ชั่นของฉัน (ซึ่งทำงานได้กับ excel) Excel ยังคงทำงานต่อไปหลังจากที่ฟังก์ชั่นสิ้นสุดลง ฉันลองทุกอย่างแล้ว! ฉันตรวจสอบอย่างละเอียดฟังก์ชั่นทั้งหมด 10 ครั้งและเพิ่ม Marshal.FinalReleaseComObject () สำหรับทุกสิ่ง! ฉันยังมี GC.Collect () และ GC.WaitForPendingFinalizers () ฉันตรวจสอบกล่องข้อความที่ซ่อนอยู่ ฉันพยายามส่งข้อความ WM_CLOSE ไปที่หน้าต่างหลักของ Excel ฉันใช้งานฟังก์ชั่นของฉันใน AppDomain แยกต่างหากและยกเลิกการโหลดโดเมนนั้น ไม่มีอะไรช่วย! ตัวเลือกที่มีการปิดอินสแตนซ์ excel ทั้งหมดไม่เหมาะสมเพราะหากผู้ใช้เริ่มต้นอินสแตนซ์ Excel อื่นด้วยตนเองในระหว่างการดำเนินการฟังก์ชั่นของฉันซึ่งใช้งานกับ Excel ได้อินสแตนซ์นั้นจะถูกปิดโดยฟังก์ชันของฉัน ฉันพนันได้เลยว่าผู้ใช้จะไม่มีความสุข! ดังนั้นโดยสุจริตนี่เป็นตัวเลือกที่อ่อนแอ (ไม่มีผู้กระทำผิด)วิธีการแก้ปัญหา : ฆ่ากระบวนการ excel โดย hWnd ของหน้าต่างหลัก (มันเป็นทางออกแรก)

นี่คือรหัสง่าย ๆ :

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

ตามที่คุณเห็นฉันมีสองวิธีตามรูปแบบลองแยกวิเคราะห์ (ฉันคิดว่ามันเหมาะสมที่นี่): วิธีการหนึ่งไม่ส่งข้อยกเว้นหากกระบวนการไม่สามารถฆ่าได้ (ตัวอย่างเช่นกระบวนการไม่มีอีกต่อไป) และวิธีการอื่นจะส่งข้อยกเว้นหากกระบวนการไม่ได้ถูกฆ่า จุดอ่อนเดียวในรหัสนี้คือสิทธิ์ความปลอดภัย ในทางทฤษฎีผู้ใช้อาจไม่มีสิทธิ์ในการฆ่ากระบวนการ แต่ใน 99.99% ของทุกกรณีผู้ใช้มีสิทธิ์ดังกล่าว ฉันทดสอบด้วยบัญชีผู้ใช้แขก - มันทำงานได้อย่างสมบูรณ์

ดังนั้นรหัสของคุณทำงานกับ Excel สามารถมีลักษณะเช่นนี้:

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 ถูกยกเลิก! :)

ตกลงกลับไปที่วิธีแก้ปัญหาที่สองตามที่ฉันสัญญาไว้ในตอนต้นของการโพสต์ วิธีที่สองคือโทร GC.Collect () และ GC.WaitForPendingFinalizers () ใช่มันใช้งานได้จริง แต่คุณต้องระวังที่นี่ด้วย!
หลายคนพูดว่า (และฉันบอกว่า) การเรียก GC.Collect () ไม่ได้ช่วยอะไร แต่เหตุผลที่มันไม่ช่วยก็คือถ้ายังมีการอ้างอิงถึงวัตถุ COM! หนึ่งในเหตุผลที่นิยมมากที่สุดสำหรับ GC.Collect () ที่ไม่เป็นประโยชน์คือการเรียกใช้โครงการในโหมดดีบั๊ก ในวัตถุโหมดดีบักที่ไม่ได้อ้างอิงจริงอีกต่อไปจะไม่ถูกรวบรวมขยะจนกว่าจะสิ้นสุดของวิธีการ
ดังนั้นหากคุณลองใช้ GC.Collect () และ GC.WaitForPendingFinalizers () และไม่ช่วยลองทำสิ่งต่อไปนี้:

1) ลองเรียกใช้โครงการของคุณในโหมด Release และตรวจสอบว่า Excel ปิดอย่างถูกต้องหรือไม่

2) ตัดวิธีการทำงานกับ Excel ในวิธีแยกต่างหาก ดังนั้นแทนที่จะเป็นดังนี้:

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

ที่คุณเขียน:

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

ตอนนี้ Excel จะปิด =)


19
เศร้าที่เธรดเก่าแล้วที่คำตอบที่ยอดเยี่ยมของคุณปรากฏว่าด้านล่างซึ่งฉันคิดว่าเป็นเหตุผลเดียวที่มันไม่ถูก upvoted อีกต่อไป ...
chiccodoro

15
ฉันต้องยอมรับว่าเมื่อฉันอ่านคำตอบของคุณครั้งแรกฉันคิดว่ามันเป็นกระบองยักษ์ หลังจากการต่อสู้กับเรื่องนี้ประมาณ 6 ชั่วโมง (ทุกอย่างได้รับการปล่อยตัวฉันไม่มีจุดสองจุด ฯลฯ ) ตอนนี้ฉันคิดว่าคำตอบของคุณคืออัจฉริยะ
ทำเครื่องหมาย

3
ขอบคุณสำหรับสิ่งนี้. เคยมวยปล้ำกับ Excel ที่ไม่ปิดไม่ว่าจะเป็นสองสามวันก่อนที่ฉันจะพบสิ่งนี้ ยอดเยี่ยม
BBlake

2
@ nightcoder: คำตอบที่ยอดเยี่ยมรายละเอียดจริงๆ ความคิดเห็นของคุณเกี่ยวกับโหมดการดีบักนั้นเป็นเรื่องจริงและสำคัญมากที่คุณชี้ให้เห็น รหัสเดียวกันในโหมดเผยแพร่มักจะใช้ได้ กระบวนการฆ่าได้ดีเมื่อใช้ระบบอัตโนมัติเท่านั้นไม่ใช่เมื่อโปรแกรมของคุณทำงานภายใน Excel เช่นเป็น COM add-in ที่มีการจัดการ
Mike Rosenblum

2
@DanM: วิธีการของคุณถูกต้อง 100% และจะไม่ล้มเหลว ด้วยการทำให้ตัวแปรทั้งหมดของคุณอยู่ภายในระบบวิธีการอ้างอิงทั้งหมดไม่สามารถเข้าถึงได้อย่างมีประสิทธิภาพโดยรหัส. NET ซึ่งหมายความว่าเมื่อส่วนสุดท้ายของคุณเรียก GC.Collect () สุดท้ายสำหรับวัตถุ COM ทั้งหมดของคุณจะได้รับการเรียกด้วยความมั่นใจ ผู้เข้ารอบสุดท้ายแต่ละคนจะโทรหา Marshal.FinalReleaseComObject เกี่ยวกับวัตถุที่กำลังจะสรุป วิธีการของคุณนั้นง่ายและไม่จริง ไม่กลัวที่จะใช้สิ่งนี้ (เฉพาะข้อแม้: ถ้าใช้ VSTO ซึ่งฉันสงสัยว่าคุณเป็นคุณจะต้องโทรหา GC.Collect () & GC.Wait สำหรับการใช้จ่ายฟินเนอร์สองครั้ง)
Mike Rosenblum

49

อัปเดต : เพิ่มรหัส C # และลิงก์ไปยังงาน Windows

ฉันใช้เวลาบางครั้งพยายามที่จะเข้าใจปัญหานี้และในเวลานั้น XtremeVBTalk เป็นคนที่กระตือรือร้นและตอบสนองมากที่สุด นี่คือเชื่อมโยงไปยังโพสต์เดิมของฉันปิดกระบวนการ Excel Interop หมดจดแม้หากเกิดปัญหาใบสมัครของคุณ ด้านล่างนี้เป็นข้อมูลสรุปของโพสต์และรหัสที่คัดลอกไปยังโพสต์นี้

  • การปิดกระบวนการ Interop ด้วยApplication.Quit()และใช้Process.Kill()งานได้เป็นส่วนใหญ่ แต่ล้มเหลวหากแอปพลิเคชันขัดข้องอย่างรุนแรง คือถ้าแอพขัดข้องกระบวนการ Excel จะยังคงทำงานหลวม
  • วิธีการแก้ปัญหาคือให้ระบบปฏิบัติการจัดการล้างกระบวนการของคุณผ่านWindows Job Objectsโดยใช้การเรียก Win32 เมื่อแอปพลิเคชันหลักของคุณตายกระบวนการที่เกี่ยวข้อง (เช่น Excel) จะถูกยกเลิกเช่นกัน

ฉันพบว่านี่เป็นโซลูชั่นที่สะอาดเพราะระบบปฏิบัติการกำลังทำการล้างข้อมูลจริง สิ่งที่คุณต้องทำคือลงทะเบียนกระบวนการ Excel

รหัสงานของ Windows

ห่อ Win32 API Calls เพื่อลงทะเบียนกระบวนการ 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);
    }

}

หมายเหตุเกี่ยวกับรหัสตัวสร้าง

  • ในนวกรรมิกinfo.LimitFlags = 0x2000;เรียกว่า 0x2000คือJOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSEค่า enum และค่านี้ถูกกำหนดโดยMSDNเป็น:

ทำให้กระบวนการทั้งหมดที่เกี่ยวข้องกับงานสิ้นสุดลงเมื่อหมายเลขอ้างอิงสุดท้ายของงานถูกปิด

การเรียก Win32 API พิเศษเพื่อรับ Process ID (PID)

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

ใช้รหัส

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

2
การฆ่าเซิร์ฟเวอร์ COM ที่ไม่ได้ดำเนินการอย่างชัดเจน (ซึ่งอาจให้บริการไคลเอ็นต์ COM อื่น ๆ ) เป็นแนวคิดที่น่ากลัว หากคุณใช้วิธีนี้เป็นเพราะโปรโตคอล COM ของคุณเสีย ไม่ควรใช้เซิร์ฟเวอร์ COM ในฐานะแอพที่มีหน้าต่างปกติ
MickyD

38

สิ่งนี้ใช้ได้กับโครงการที่ฉันกำลังทำอยู่:

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

เราเรียนรู้ว่าการตั้งค่าการอ้างอิงทุกอย่างให้กับวัตถุ Excel COM เป็นโมฆะเมื่อคุณทำเสร็จแล้ว ซึ่งรวมถึงเซลล์แผ่นงานและทุกอย่าง


30

สิ่งใดก็ตามที่อยู่ในเนมสเปซ Excel ต้องได้รับการเผยแพร่ ระยะเวลา

คุณทำไม่ได้:

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

คุณต้องทำ

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

ตามด้วยการปล่อยของวัตถุ


3
วิธี aobut xlRange.Interior.Color ตัวอย่างเช่น
HAdes

การตกแต่งภายในจะต้องมีการเปิดตัว (ในเนมสเปซ) ... สีในทางกลับกันไม่ได้ (สาเหตุมาจาก System.Drawing.Color, iirc)
MagicKat

2
จริง ๆ แล้วสีคือสีของ Excel ไม่ใช่สี. สุทธิ คุณเพิ่งผ่าน Long ยังกำหนดสมุดงาน จำเป็นต้องได้รับการปล่อยตัวเวิร์กชีต ... น้อยไปกว่านั้น
ไม่ระบุตัวตนพิมพ์

นั่นไม่เป็นความจริงกฎสองจุด - ไม่ดีจุดหนึ่งจุดที่ดีคือเรื่องไร้สาระ นอกจากนี้คุณไม่จำเป็นต้องปล่อยสมุดงานแผ่นงานช่วงที่เป็นงานของ GC สิ่งเดียวที่คุณต้องไม่ลืมคือเรียก Quit บนวัตถุ Excel.Application หนึ่งเดียว หลังจากเรียก Quit ให้ลบอ็อบเจกต์ Excel.Application และโทร GC.Collect (); GC.WaitForPendingFinalizers (); สองครั้ง
Dietrich Baumgarten

29

ก่อนอื่น - คุณไม่ต้องโทรMarshal.ReleaseComObject(...)หรือMarshal.FinalReleaseComObject(...)เมื่อใช้ Excel interop มันเป็นรูปแบบการต่อต้านที่สับสน แต่ข้อมูลใด ๆ เกี่ยวกับเรื่องนี้รวมถึงจาก Microsoft ที่ระบุว่าคุณต้องปล่อยการอ้างอิง COM จาก. NET ด้วยตนเองไม่ถูกต้อง ความจริงก็คือ. NET runtime และตัวรวบรวมขยะอย่างถูกต้องติดตามและล้างการอ้างอิง COM สำหรับรหัสของคุณหมายความว่าคุณสามารถลบลูป `while (()) ทั้งหมดที่ด้านบน

ประการที่สองถ้าคุณต้องการให้แน่ใจว่า COM อ้างอิงไปยังวัตถุ COM ที่อยู่นอกกระบวนการจะถูกล้างข้อมูลเมื่อกระบวนการของคุณสิ้นสุดลง (เพื่อให้กระบวนการ Excel จะปิด) คุณต้องแน่ใจว่าตัวรวบรวมขยะทำงาน คุณทำเช่นนี้ได้อย่างถูกต้องด้วยการโทรไปและGC.Collect() GC.WaitForPendingFinalizers()การเรียกสองครั้งนี้ปลอดภัยและทำให้แน่ใจว่ารอบการทำความสะอาดนั้นแน่นอนเช่นกัน (แม้ว่าฉันไม่แน่ใจว่าจำเป็นและจะขอบคุณตัวอย่างที่แสดงให้เห็นนี้)

ประการที่สามเมื่อทำงานภายใต้ดีบักเกอร์การอ้างอิงในเครื่องจะยังคงมีชีวิตอยู่จนกว่าจะสิ้นสุดกระบวนการ (เพื่อให้การตรวจสอบตัวแปรภายในเครื่องทำงาน) ดังนั้นการGC.Collect()โทรจึง ไม่มีประสิทธิภาพในการทำความสะอาดวัตถุเช่นrng.Cellsเดียวกับวิธีการเดียวกัน คุณควรแบ่งรหัสที่ทำการ COM interop จากการล้าง GC เป็นวิธีแยกต่างหาก (นี่เป็นการค้นพบที่สำคัญสำหรับฉันจากส่วนหนึ่งของคำตอบที่โพสต์ที่นี่โดย @nightcoder)

รูปแบบทั่วไปจึงจะเป็น:

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

มีข้อมูลเท็จและความสับสนมากมายเกี่ยวกับปัญหานี้รวมถึงโพสต์มากมายใน MSDN และใน Stack Overflow (และโดยเฉพาะคำถามนี้!)

ในที่สุดสิ่งที่ทำให้ฉันมั่นใจในการมองอย่างใกล้ชิดและคิดออกคำแนะนำที่ถูกต้องคือโพสต์บล็อกMarshal.ReleaseComObject พิจารณาว่าเป็นอันตรายพร้อมกับค้นหาปัญหาที่มีการอ้างอิงอยู่ภายใต้ดีบักเกอร์ซึ่งทำให้การทดสอบก่อนหน้าของฉันสับสน


5
คำตอบทั้งหมดในหน้านี้ไม่ถูกต้องยกเว้นหน้านี้ พวกเขาทำงาน แต่ทำงานมากเกินไป IGNORE กฎ "2 DOTS" ให้ GC ทำงานให้คุณ หลักฐานเพิ่มเติมจาก. Net GURU Hans Passant: stackoverflow.com/a/25135685/3852958
Alan Baljeu

1
Govert สิ่งที่โล่งใจที่จะหาวิธีที่ถูกต้องที่จะทำ ขอบคุณ.
Dietrich Baumgarten

18

ฉันพบแม่แบบทั่วไปที่มีประโยชน์ที่สามารถช่วยในการใช้รูปแบบการกำจัดที่ถูกต้องสำหรับวัตถุ COM ที่ต้อง Marshal.ReleaseComObject เรียกว่าเมื่อพวกเขาออกจากขอบเขต:

การใช้งาน:

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

แม่แบบ:

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

อ้างอิง:

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


3
yup อันนี้เป็นสิ่งที่ดี ฉันคิดว่ารหัสนี้สามารถปรับปรุงได้แม้ว่าตอนนี้ FinalReleaseCOMObject จะพร้อมใช้งาน
ไม่ระบุชื่อประเภท

ยังคงน่ารำคาญที่มีบล็อกการใช้งานสำหรับแต่ละวัตถุ ดูที่นี่stackoverflow.com/questions/2191489/…สำหรับทางเลือกอื่น
Henrik

16

ฉันไม่เชื่อว่าปัญหานี้ได้หลอกหลอนโลกเป็นเวลา 5 ปี .... ถ้าคุณสร้างแอปพลิเคชั่นคุณต้องปิดมันก่อนที่จะลบลิงค์ออก

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

เมื่อปิด

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

เมื่อคุณใหม่แอปพลิเคชั่น excel มันจะเปิดโปรแกรม excel ในพื้นหลัง คุณต้องสั่งให้โปรแกรม excel ออกก่อนที่จะปล่อยลิงค์เนื่องจากโปรแกรม excel นั้นไม่ได้เป็นส่วนหนึ่งของการควบคุมโดยตรงของคุณ ดังนั้นมันจะยังคงเปิดอยู่หากปล่อยลิงก์!

การเขียนโปรแกรมที่ดีทุกคน ~~


ไม่คุณไม่ต้องทำโดยเฉพาะอย่างยิ่งหากคุณต้องการอนุญาตให้ผู้ใช้โต้ตอบกับแอปพลิเคชัน COM (Excel ในกรณีนี้ - OP ไม่ได้ระบุว่ามีการโต้ตอบกับผู้ใช้หรือไม่ แต่ไม่ได้อ้างอิงเพื่อปิดเครื่อง) คุณเพียงแค่ต้องให้ผู้โทรปล่อยให้ผู้ใช้สามารถออกจาก Excel เมื่อปิดพูดไฟล์เดียว
downwitch

สิ่งนี้ทำงานได้อย่างสมบูรณ์แบบสำหรับฉัน - ชนเมื่อฉันมี objExcel.Quit () เท่านั้น แต่เมื่อฉันยังทำ objExcel.Application.Quit () ไว้ล่วงหน้าให้ปิดอย่างเรียบร้อย
mcmillab

13

นักพัฒนาทั่วไปไม่มีการแก้ปัญหาของคุณทำงานสำหรับฉันดังนั้นฉันตัดสินใจที่จะใช้ใหม่เคล็ดลับ

ก่อนอื่นให้ระบุ "เป้าหมายของเราคืออะไร" => "ไม่เห็นวัตถุ excel หลังจากงานของเราในตัวจัดการงาน"

ตกลง. อย่าปล่อยให้ท้าทายและเริ่มทำลายมัน แต่อย่าพิจารณาทำลายอินสแตนซ์อื่น ๆ ของ Excel ที่ทำงานแบบขนาน

ดังนั้นรับรายการโปรเซสเซอร์ปัจจุบันและดึง PID ของกระบวนการ EXCEL จากนั้นเมื่องานของคุณเสร็จสิ้นเรามีแขกใหม่ในรายการกระบวนการด้วย PID ที่ไม่ซ้ำกันค้นหาและทำลายเพียงแค่นั้น

<โปรดระลึกไว้ว่ากระบวนการ excel ใหม่ใด ๆ ในระหว่างงาน excel ของคุณจะถูกตรวจพบว่าใหม่และถูกทำลาย> <ทางออกที่ดีกว่าคือการดักจับ PID ของวัตถุ excel ที่สร้างขึ้นใหม่และเพิ่งทำลายมัน>

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

สิ่งนี้จะช่วยแก้ไขปัญหาของฉันหวังว่าคุณก็เช่นกัน


1
.... นี่เป็นวิธีการเลื่อนแบบเล็กน้อย? - มันคล้ายกับสิ่งที่ฉันใช้ :( แต่ต้องเปลี่ยนปัญหากับการใช้วิธีนี้ก็คือบ่อยครั้งเมื่อเปิด excel บนเครื่องที่มีสิ่งนี้วิ่งมันมีการเตือนในแถบด้านซ้ายมือ "Excel ถูกปิดลงอย่างไม่ถูกต้อง": ไม่สง่างามมากฉันคิดว่าหนึ่งในคำแนะนำก่อนหน้านี้ในกระทู้นี้จะเป็นที่ต้องการ
ทำไม theq

11

แน่นอนว่าดูเหมือนว่ามันซับซ้อนเกินไป จากประสบการณ์ของฉันมีเพียงสามสิ่งสำคัญที่จะทำให้ Excel ปิดอย่างถูกต้อง:

1: ตรวจสอบให้แน่ใจว่าไม่มีการอ้างอิงที่เหลืออยู่ในแอปพลิเคชัน excel ที่คุณสร้างขึ้น (คุณควรมีเพียงอย่างใดอย่างหนึ่งอยู่แล้วตั้งค่าเป็นnull)

2: โทร GC.Collect()

3: Excel ต้องถูกปิดโดยผู้ใช้ปิดโปรแกรมด้วยตนเองหรือโดยการโทร Quitบนวัตถุ Excel (โปรดทราบว่าQuitจะทำงานเหมือนกับว่าผู้ใช้พยายามปิดโปรแกรมและจะแสดงกล่องโต้ตอบการยืนยันหากมีการเปลี่ยนแปลงที่ยังไม่ได้บันทึกแม้ว่าจะมองไม่เห็น Excel ผู้ใช้สามารถกดยกเลิกแล้ว Excel จะไม่ถูกปิด )

1 ต้องเกิดขึ้นก่อน 2 แต่ 3 สามารถเกิดขึ้นได้ทุกที่ทุกเวลา

วิธีหนึ่งในการใช้สิ่งนี้คือการห่อวัตถุ interop Excel กับคลาสของคุณเองสร้างอินสแตนซ์ของ interop ในตัวสร้างและใช้ IDisposable ด้วยการกำจัดมองหาสิ่งที่ต้องการ

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

ที่จะทำความสะอาด excel จากด้านโปรแกรมของคุณ เมื่อ Excel ถูกปิด (ผู้ใช้หรือคุณโทรด้วยตนเองQuit ) กระบวนการจะหายไป หากโปรแกรมถูกปิดไปแล้วกระบวนการจะหายไปจากการGC.Collect()โทร

(ฉันไม่แน่ใจว่ามันสำคัญขนาดไหน แต่คุณอาจต้องการGC.WaitForPendingFinalizers()สายหลังจากGC.Collect()โทร แต่ไม่จำเป็นอย่างยิ่งที่จะต้องกำจัดกระบวนการ Excel)

สิ่งนี้ได้ผลสำหรับฉันโดยไม่มีปัญหามานานหลายปี โปรดทราบว่าในขณะที่ใช้งานได้จริงคุณต้องปิดอย่างสง่างามเพื่อให้การทำงาน คุณจะยังคงได้รับกระบวนการ excel.exe สะสมหากคุณขัดจังหวะโปรแกรมของคุณก่อนที่ Excel จะล้างข้อมูล (โดยปกติจะกดปุ่ม "หยุด" ในขณะที่โปรแกรมของคุณกำลังดีบั๊ก)


1
คำตอบนี้ใช้ได้ดีและสะดวกกว่าคำตอบที่ยอมรับ ไม่ต้องกังวลเกี่ยวกับ "จุดสองจุด" กับวัตถุ COM Marshalใช้ไม่มี
andrew.cuthbert

9

ในการเพิ่มเหตุผลที่ทำให้ Excel ไม่ปิดแม้ว่าคุณจะสร้างการอ้างอิงโดยตรงไปยังวัตถุแต่ละชิ้นเมื่ออ่านสร้างเป็นห่วงสำหรับ 'สำหรับ'

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

และอีกเหตุผลหนึ่งให้ใช้การอ้างอิงอีกครั้งโดยไม่ปล่อยค่าก่อนหน้ามาก่อน
กริมฟอร์ต

ขอบคุณ. ฉันยังใช้ "สำหรับ" วิธีการแก้ปัญหาของคุณทำงานให้ฉันด้วย
Chad Braun-Duin

+1 ขอบคุณ นอกจากนี้โปรดทราบว่าถ้าคุณมีวัตถุสำหรับช่วง Excel และคุณต้องการเปลี่ยนช่วงในช่วงอายุการใช้งานของวัตถุฉันพบว่าฉันต้องปล่อย ReleaseComObject ก่อนกำหนดใหม่ซึ่งทำให้รหัสไม่เรียบร้อยเล็กน้อย!
AjV Jsy

9

ฉันเคยปฏิบัติตามคำแนะนำที่พบในคำตอบของ VVSคำตอบของอย่างไรก็ตามในความพยายามที่จะรักษาคำตอบล่าสุดด้วยตัวเลือกล่าสุดฉันคิดว่าโครงการในอนาคตของฉันทั้งหมดจะใช้ไลบรารี "NetOffice"

NetOfficeเป็นการทดแทนที่สมบูรณ์สำหรับ Office PIAs และเป็นรุ่นที่ไม่เชื่อเรื่องพระเจ้าอย่างสมบูรณ์ มันคือชุดของ Managed COM wrappers ที่สามารถจัดการกับการล้างข้อมูลที่มักทำให้เกิดอาการปวดหัวเมื่อทำงานกับ Microsoft Office ใน. NET

คุณสมบัติที่สำคัญบางประการคือ:

  • ส่วนใหญ่ไม่ขึ้นกับเวอร์ชัน (และคุณสมบัติที่ขึ้นกับเวอร์ชันจะได้รับการบันทึกไว้)
  • ไม่มีการพึ่งพา
  • ไม่มี PIA
  • ไม่มีการลงทะเบียน
  • ไม่มี VSTO

ฉันไม่มีส่วนเกี่ยวข้องกับโครงการ ฉันแค่ชื่นชมการลดอาการปวดหัวอย่างสิ้นเชิง


2
ควรทำเครื่องหมายเป็นคำตอบจริง ๆ NetOffice ย่อความซับซ้อนทั้งหมดนี้ออกไป
C. Augusto Proiete

1
ฉันใช้ NetOffice เป็นระยะเวลานานในการเขียนโปรแกรมเสริม Excel และทำงานได้อย่างสมบูรณ์แบบ สิ่งเดียวที่ต้องพิจารณาคือถ้าคุณไม่ทิ้งวัตถุที่ใช้แล้วอย่างชัดเจนมันจะทำเมื่อคุณออกจากแอปพลิเคชัน ดังนั้นกฎของหัวแม่มือกับ NetOffice อยู่เสมอกับการใช้ "ใช้" รูปแบบกับทุกวัตถุ Excel เช่นเซลล์ช่วงหรือแผ่น ฯลฯ
Stas Ivanov

8

คำตอบที่ยอมรับได้ที่นี่นั้นถูกต้อง แต่ต้องจำไว้ว่าไม่เพียง แต่ต้องหลีกเลี่ยงการอ้างอิง "สองจุด" เท่านั้น แต่ยังต้องหลีกเลี่ยงวัตถุที่ถูกดึงผ่านดัชนีด้วย คุณไม่จำเป็นต้องรอจนกว่าโปรแกรมจะทำความสะอาดวัตถุเหล่านี้ให้เสร็จสิ้นคุณควรสร้างฟังก์ชั่นที่จะทำความสะอาดทันทีที่คุณทำเสร็จเมื่อเป็นไปได้ นี่คือฟังก์ชั่นที่ฉันสร้างขึ้นซึ่งกำหนดคุณสมบัติบางอย่างของวัตถุสไตล์ที่เรียกว่า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 { } 
}

สังเกตว่าฉันต้องตั้ง xlBorders[Excel.XlBordersIndex.xlEdgeBottom]ตัวแปรเพื่อล้างค่านั้น (ไม่ใช่เพราะทั้งสองจุดซึ่งหมายถึงการแจงนับซึ่งไม่จำเป็นต้องได้รับการปล่อยตัวออกมา แต่เนื่องจากวัตถุที่ฉันอ้างถึงเป็นวัตถุชายแดนจริงๆแล้ว ที่จะต้องมีการเปิดตัว)

สิ่งนี้ไม่จำเป็นในแอปพลิเคชั่นมาตรฐานซึ่งทำงานได้ดีมากในการทำความสะอาดหลังจากตัวเอง แต่ในแอปพลิเคชัน ASP.NET ถ้าคุณพลาดแม้แต่หนึ่งในนั้นไม่ว่าคุณจะเรียกเก็บขยะบ่อยแค่ไหนก็ตาม ยังคงทำงานบนเซิร์ฟเวอร์ของคุณ

มันต้องการความสนใจอย่างมากในรายละเอียดและการทดสอบจำนวนมากในขณะที่ตรวจสอบตัวจัดการงานเมื่อเขียนรหัสนี้ แต่การทำเช่นนั้นจะช่วยให้คุณไม่ต้องยุ่งยากในการค้นหาหน้าเพจของโค้ดเพื่อค้นหาสิ่งที่คุณพลาดไป สิ่งนี้มีความสำคัญอย่างยิ่งเมื่อทำงานในลูปที่คุณต้องปล่อยแต่ละตำแหน่งของวัตถุแม้ว่าจะใช้ชื่อตัวแปรเดียวกันทุกครั้งที่ลูป


ภายใน Release ให้ตรวจสอบ Null (คำตอบของ Joe) ที่จะหลีกเลี่ยงข้อยกเว้น null ที่ไม่จำเป็น ฉันได้ทดสอบวิธีการมากมาย นี่เป็นวิธีเดียวในการปล่อย Excel อย่างมีประสิทธิภาพ
แกร์ฮาร์ดพาวเวลล์

8

หลังจากลองแล้ว

  1. ปล่อยวัตถุ COM ในลำดับที่กลับกัน
  2. เพิ่มGC.Collect()และGC.WaitForPendingFinalizers()สองครั้งในตอนท้าย
  3. ไม่เกินสองจุด
  4. ปิดสมุดงานและออกจากแอปพลิเคชัน
  5. ทำงานในโหมดที่วางจำหน่าย

ทางออกสุดท้ายสำหรับฉันคือการย้ายชุดหนึ่ง

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

ที่เราเพิ่มไปยังจุดสิ้นสุดของฟังก์ชั่นเพื่อห่อหุ้มดังนี้:

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

ฉันพบว่าฉันไม่จำเป็นต้องลบล้าง ComObjects เพียงเลิก () - ปิด () และ FinalReleaseComObject ฉันไม่อยากจะเชื่อเลยว่าทั้งหมดนี้คือสิ่งที่ขาดหายไปในตอนท้ายเพื่อให้มันทำงานได้ ที่ดี!
Carol

7

ฉันทำตามนี้แล้ว ... แต่ฉันก็ยังเจอปัญหา 1 จาก 1,000 ครั้ง ใครจะรู้ว่าทำไม ได้เวลานำค้อนออกมา ...

หลังจากคลาสแอปพลิเคชัน Excel ถูกสร้างอินสแตนซ์ฉันได้รับกระบวนการ Excel ที่เพิ่งสร้างขึ้น

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

จากนั้นเมื่อฉันทำความสะอาด COM ทั้งหมดข้างต้นเสร็จแล้วฉันต้องแน่ใจว่ากระบวนการไม่ทำงาน หากยังคงทำงานอยู่ให้ฆ่ามัน!

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

7

¨°º¤ø„ ¸ Shoot Excel proc และหมากฝรั่งบับเบิ้ลชิว¸„ ø¤º°¨

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

6

คุณต้องระวังว่า Excel นั้นไวต่อวัฒนธรรมที่คุณใช้อยู่

คุณอาจพบว่าคุณต้องตั้งค่าวัฒนธรรมเป็น EN-US ก่อนที่จะเรียกใช้ฟังก์ชัน Excel สิ่งนี้ไม่ได้ใช้กับฟังก์ชั่นทั้งหมด - แต่บางอย่าง

    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;

สิ่งนี้ใช้ได้แม้ว่าคุณจะใช้ VSTO

สำหรับรายละเอียด: http://support.microsoft.com/default.aspx?scid=kb;en-us;Q320369


6

"อย่าใช้จุดสองจุดกับวัตถุ COM" เป็นกฎง่ายๆในการหลีกเลี่ยงการรั่วไหลของการอ้างอิง COM แต่ Excel PIA สามารถนำไปสู่การรั่วไหลได้หลายวิธีมากกว่าที่เห็นตั้งแต่แรกเห็น

หนึ่งในวิธีเหล่านี้คือการสมัครรับกิจกรรมใด ๆ ที่แสดงโดยวัตถุ COM ของโมเดลวัตถุ Excel ใด ๆ

ตัวอย่างเช่นการสมัครสมาชิกกิจกรรม WorkbookOpen ของคลาสแอปพลิเคชัน

ทฤษฎีบางอย่างเกี่ยวกับเหตุการณ์ COM

คลาส COM แสดงกลุ่มของเหตุการณ์ผ่านอินเทอร์เฟซการโทรกลับ เพื่อสมัครสมาชิกกับเหตุการณ์รหัสลูกค้าสามารถลงทะเบียนวัตถุที่ใช้อินเตอร์เฟซการโทรกลับและชั้น COM จะเรียกวิธีการในการตอบสนองต่อเหตุการณ์ที่เฉพาะเจาะจง เนื่องจากอินเทอร์เฟซการเรียกกลับเป็นอินเทอร์เฟซ COM เป็นหน้าที่ของวัตถุที่ใช้งานเพื่อลดจำนวนการอ้างอิงของวัตถุ COM ใด ๆ ที่ได้รับ (เป็นพารามิเตอร์) สำหรับตัวจัดการเหตุการณ์ใด ๆ

Excel PIA แสดงกิจกรรม COM อย่างไร

Excel PIA แสดงกิจกรรม COM ของคลาส Excel Application เป็นเหตุการณ์. NET ทั่วไป เมื่อใดก็ตามที่รหัสลูกค้าสมัครเหตุการณ์สุทธิ (เน้น 'a') PIA สร้างอินสแตนซ์ของชั้นการใช้อินเตอร์เฟซที่โทรกลับและลงทะเบียนกับ Excel

ดังนั้นวัตถุหมายเลขโทรกลับได้รับการลงทะเบียนกับ Excel เพื่อตอบสนองการร้องขอการสมัครสมาชิกที่แตกต่างจากรหัส. NET วัตถุโทรกลับหนึ่งรายการต่อการสมัครกิจกรรม

อินเทอร์เฟซการเรียกกลับสำหรับการจัดการเหตุการณ์หมายความว่า PIA ต้องสมัครสมาชิกกิจกรรมอินเทอร์เฟซทั้งหมดสำหรับคำขอการสมัครสมาชิกกิจกรรม. NET ทุกครั้ง มันไม่สามารถเลือกและเลือก ในการรับเหตุการณ์ call-back วัตถุ call-back จะตรวจสอบว่าตัวจัดการเหตุการณ์. NET ที่เกี่ยวข้องสนใจในเหตุการณ์ปัจจุบันหรือไม่และจากนั้นจะเรียกใช้ตัวจัดการหรือละเว้นการติดต่อกลับ

ผลต่อจำนวนการอ้างอิงอินสแตนซ์ COM

วัตถุการเรียกกลับเหล่านี้ทั้งหมดไม่ลดจำนวนการอ้างอิงของวัตถุ COM ใด ๆ ที่พวกเขาได้รับ (เป็นพารามิเตอร์) สำหรับวิธีการโทรกลับใด ๆ (แม้สำหรับวัตถุที่ถูกปฏิเสธอย่างเงียบ ๆ ) พวกเขาใช้ แต่เพียงผู้เดียวในตัวรวบรวมขยะCLRเพื่อเพิ่มวัตถุ COM

เนื่องจากการรัน GC นั้นไม่สามารถกำหนดได้สิ่งนี้สามารถนำไปสู่การหยุดกระบวนการ Excel ในระยะเวลานานกว่าที่ต้องการและสร้างการแสดงผลของ 'การรั่วไหลของหน่วยความจำ'

สารละลาย

วิธีแก้ปัญหาเดียวในตอนนี้คือการหลีกเลี่ยงผู้ให้บริการเหตุการณ์ของ PIA สำหรับคลาส COM และเขียนผู้ให้บริการเหตุการณ์ของคุณซึ่งกำหนดวัตถุ COM ที่กำหนดไว้ล่วงหน้า

สำหรับการเรียนแอพลิเคชันนี้สามารถทำได้โดยการใช้อินเตอร์เฟซ AppEvents และจากนั้นการลงทะเบียนการดำเนินการกับ Excel โดยใช้อินเตอร์เฟซ IConnectionPointContainer คลาสแอปพลิเคชัน (และสำหรับวัตถุ COM ทั้งหมดที่เปิดเผยกิจกรรมโดยใช้กลไกการโทรกลับ) ใช้ส่วนต่อประสาน IConnectionPointContainer


4

เป็นคนอื่นได้ชี้ให้เห็นคุณจำเป็นต้องสร้างการอ้างอิงที่ชัดเจนสำหรับทุกวัตถุ Excel ที่คุณใช้และเรียก Marshal.ReleaseComObject ในการอ้างอิงว่าตามที่อธิบายไว้ในบทความนี้ KB คุณต้องลองใช้ / ในที่สุดเพื่อให้แน่ใจว่ามีการเรียก ReleaseComObject เสมอแม้ว่าจะมีข้อผิดพลาดเกิดขึ้นก็ตาม เช่นแทนที่จะ:

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

คุณต้องทำสิ่งที่ชอบ:

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

คุณต้องเรียก Application.Quit ก่อนที่จะปล่อยวัตถุ Application ถ้าคุณต้องการปิด Excel

อย่างที่คุณเห็นสิ่งนี้กลายเป็นความไม่สะดวกอย่างรวดเร็วเมื่อคุณพยายามทำอะไรที่ซับซ้อนพอสมควร ฉันได้พัฒนาแอปพลิเคชั่น. NET ด้วยคลาส wrapper ง่าย ๆ ที่ห่อหุ้มการจัดการง่ายๆของโมเดลวัตถุ Excel (เปิดเวิร์กบุ๊กเขียนไปที่ Range บันทึก / ปิดเวิร์กบุ๊กเป็นต้น) คลาส wrapper ใช้ IDisposable อย่างระมัดระวังนำ Marshal.ReleaseComObject ไปใช้กับทุกวัตถุที่ใช้และจะไม่เปิดเผยวัตถุ Excel ใด ๆ ไปยังส่วนที่เหลือของแอป

แต่วิธีการนี้ไม่ได้ปรับขนาดได้ดีสำหรับความต้องการที่ซับซ้อนมากขึ้น

นี่คือการขาดใหญ่ของ. NET COM Interop สำหรับสถานการณ์ที่ซับซ้อนมากขึ้นฉันจะพิจารณาเขียน ActiveX DLL ใน VB6 หรือภาษาที่ไม่มีการจัดการอื่น ๆ อย่างจริงจังซึ่งคุณสามารถมอบหมายการโต้ตอบทั้งหมดกับวัตถุ COM ที่ล้าสมัยเช่น Office จากนั้นคุณสามารถอ้างอิง ActiveX DLL นี้จากแอปพลิเคชั่น. NET ของคุณและสิ่งต่างๆจะง่ายขึ้นมากเนื่องจากคุณจะต้องเปิดใช้การอ้างอิงนี้เพียงครั้งเดียวเท่านั้น


3

เมื่อทุกอย่างข้างต้นใช้งานไม่ได้ให้ลองใช้ Excel เพื่อปิดแผ่นงาน:

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

...
FinalReleaseComObject(app);

2
ฉันเกลียดการรอคอยตามอำเภอใจ แต่ +1 เพราะคุณถูกต้องเกี่ยวกับการรอให้สมุดงานปิด อีกทางเลือกหนึ่งคือการสำรวจความคิดเห็นของคอลเลกชันสมุดงานในการวนซ้ำและใช้การรอตามอำเภอใจเป็นการหมดเวลาของการวนรอบ
dFlat

3

ตรวจสอบให้แน่ใจว่าคุณปล่อยวัตถุทั้งหมดที่เกี่ยวข้องกับ Excel!

ฉันใช้เวลาสองสามชั่วโมงโดยพยายามหลายวิธี ทั้งหมดเป็นความคิดที่ดี แต่ในที่สุดฉันก็พบว่ามีข้อผิดพลาด: ถ้าคุณไม่ปล่อยวัตถุทั้งหมดไม่มีวิธีใดที่จะช่วยคุณได้ในกรณีของฉัน ให้แน่ใจว่าคุณปล่อยวัตถุทั้งหมดรวมถึงช่วงที่หนึ่ง!

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

ตัวเลือกที่มีอยู่ด้วยกันที่นี่


จุดดีมาก! ฉันคิดว่าเพราะฉันใช้ Office 2007 มีการทำความสะอาดในนามของฉัน ฉันใช้คำแนะนำเพิ่มเติม แต่ไม่ได้เก็บตัวแปรตามที่คุณแนะนำไว้ที่นี่และ EXCEL.EXE ออก แต่ฉันอาจโชคดีและถ้าฉันมีปัญหาเพิ่มเติมฉันจะดูส่วนนี้ของรหัส =)
Coops

3

บทความที่ยอดเยี่ยมเกี่ยวกับการปล่อยวัตถุ COM คือ2.5 การปล่อยวัตถุ COM (MSDN)

วิธีการที่ฉันจะสนับสนุนคือการลบ Excel ของคุณอ้างอิงช่วงถ้าพวกเขาเป็นตัวแปรที่ไม่ใช่ในท้องถิ่นแล้วโทรGC.Collect()และGC.WaitForPendingFinalizers()สองครั้ง ตัวแปร Interop ที่มีการกำหนดขอบเขตในท้องถิ่นจะได้รับการดูแลโดยอัตโนมัติ

สิ่งนี้ทำให้ไม่จำเป็นต้องเก็บการอ้างอิงที่มีชื่อสำหรับทุก ๆวัตถุ COMรายการ

นี่คือตัวอย่างที่นำมาจากบทความ:

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

คำเหล่านี้ตรงจากบทความ:

ในเกือบทุกสถานการณ์การลบการอ้างอิง RCW และการบังคับให้มีการรวบรวมขยะจะล้างอย่างถูกต้อง หากคุณโทรหา GC.WaitForPendingFinalizers การรวบรวมขยะจะถูกกำหนดไว้อย่างที่คุณสามารถทำได้ นั่นคือคุณจะค่อนข้างแน่ใจว่าเมื่อวัตถุได้รับการทำความสะอาด - เมื่อกลับจากการเรียกครั้งที่สองไปยัง WaitForPendingFinalizers คุณสามารถใช้ Marshal.ReleaseComObject อย่างไรก็ตามโปรดทราบว่าคุณไม่จำเป็นต้องใช้วิธีนี้มากนัก


2

กฎสองจุดไม่ทำงานสำหรับฉัน ในกรณีของฉันฉันสร้างวิธีการล้างทรัพยากรของฉันดังนี้:

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

2

ทางออกของฉัน

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

2

คุณควรระมัดระวังในการใช้แอพพลิเคชั่นการทำงานร่วมกันระหว่าง Word / Excel หลังจากลองใช้วิธีแก้ปัญหาทั้งหมดเรายังคงมีกระบวนการ "WinWord" จำนวนมากที่เปิดทิ้งไว้บนเซิร์ฟเวอร์ (มีผู้ใช้มากกว่า 2,000 ราย)

หลังจากทำงานกับปัญหาเป็นเวลาหลายชั่วโมงฉันรู้ว่าถ้าฉันเปิดเอกสารมากกว่าสองสามไฟล์ที่ใช้Word.ApplicationClass.Document.Open()ในเธรดที่ต่างกันกระบวนการของผู้ปฏิบัติงาน IIS (w3wp.exe) จะล้มเหลวทำให้กระบวนการ WinWord ทั้งหมดเปิดขึ้น!

ดังนั้นฉันคิดว่าไม่มีวิธีแก้ปัญหาที่แน่นอนสำหรับปัญหานี้ แต่เปลี่ยนเป็นวิธีอื่นเช่นการพัฒนาOffice Open XML


1

คำตอบที่ยอมรับไม่ได้ผลสำหรับฉัน โค้ดต่อไปนี้ใน destructor ทำงาน

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


1

ขณะนี้ฉันกำลังทำงานกับ Office automation และได้พบกับโซลูชันที่ใช้งานได้ทุกครั้งสำหรับฉัน มันง่ายและไม่เกี่ยวข้องกับการฆ่ากระบวนการใด ๆ

ดูเหมือนว่าเพียงแค่วนลูปผ่านกระบวนการที่ใช้งานอยู่ในปัจจุบันและในทางใดทางหนึ่ง 'เข้าถึง' กระบวนการ Excel แบบเปิดอินสแตนซ์ใด ๆ ที่ห้อยอยู่ของ Excel จะถูกลบออก รหัสด้านล่างเพียงตรวจสอบกระบวนการที่ชื่อเป็น 'Excel' แล้วเขียนคุณสมบัติ MainWindowTitle ของกระบวนการไปยังสตริง 'การโต้ตอบ' กับกระบวนการนี้ทำให้ Windows ติดตามและยกเลิกอินสแตนซ์ที่แช่แข็งของ Excel

ฉันเรียกใช้วิธีการด้านล่างก่อนเพิ่มในที่ฉันกำลังพัฒนาเลิกเนื่องจากไฟมันเหตุการณ์ขนถ่าย จะลบอินสแตนซ์ที่แขวนอยู่ของ Excel ทุกครั้ง ในความซื่อสัตย์ทั้งหมดฉันไม่แน่ใจทั้งหมดว่าทำไมงานนี้ แต่มันทำงานได้ดีสำหรับฉันและสามารถวางในตอนท้ายของโปรแกรม Excel ใด ๆ โดยไม่ต้องกังวลเกี่ยวกับจุดสองจุด Marshal.ReleaseComObject หรือกระบวนการฆ่า ฉันจะสนใจคำแนะนำใด ๆ ว่าทำไมสิ่งนี้ถึงมีประสิทธิภาพ

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

1

ฉันคิดว่านั่นเป็นเพียงวิธีที่กรอบการทำงานของแอปพลิเคชัน Office แต่ฉันอาจผิด ในบางวันบางแอปพลิเคชันจะล้างข้อมูลกระบวนการโดยทันทีและในวันอื่น ๆ ดูเหมือนว่าจะรอจนกว่าแอปพลิเคชันจะปิด โดยทั่วไปฉันเลิกใส่ใจกับรายละเอียดและตรวจสอบให้แน่ใจว่าไม่มีกระบวนการพิเศษใด ๆ ที่ลอยอยู่รอบ ๆ ในตอนท้ายของวัน

นอกจากนี้และบางทีฉันอาจจะทำให้เรื่องง่ายขึ้น แต่ฉันคิดว่าคุณทำได้ ...

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

อย่างที่ฉันพูดไปก่อนหน้านี้ฉันไม่ได้สนใจรายละเอียดว่ากระบวนการ Excel ปรากฏขึ้นหรือหายไปเมื่อใด แต่โดยปกติแล้วจะได้ผลสำหรับฉัน ฉันไม่ต้องการให้กระบวนการของ Excel ทำสิ่งอื่นใดนอกจากเวลาเพียงเล็กน้อย แต่ฉันอาจเป็นแค่คนหวาดระแวงในเรื่องนั้น


1

ตามที่บางคนอาจเขียนไปแล้วมันไม่สำคัญเพียงแค่คุณปิด Excel (วัตถุ); เป็นสิ่งสำคัญเช่นกันที่คุณเปิดมันและตามประเภทของโครงการ

ในแอปพลิเคชัน WPF โดยทั่วไปรหัสเดียวกันนั้นทำงานได้โดยไม่มีปัญหาเล็กน้อย

ฉันมีโครงการที่กำลังประมวลผลไฟล์ Excel เดียวกันหลายครั้งสำหรับค่าพารามิเตอร์ที่แตกต่าง - เช่นการแยกวิเคราะห์ตามค่าในรายการทั่วไป

ฉันใส่ฟังก์ชันที่เกี่ยวข้องกับ Excel ทั้งหมดลงในคลาสพื้นฐานและแยกวิเคราะห์ลงในคลาสย่อย (ตัวแยกวิเคราะห์ที่แตกต่างกันใช้ฟังก์ชัน Excel ทั่วไป) ฉันไม่ต้องการให้ Excel เปิดและปิดอีกครั้งสำหรับแต่ละรายการในรายการทั่วไปดังนั้นฉันจึงเปิดเพียงครั้งเดียวในคลาสพื้นฐานและปิดในคลาสย่อย ฉันมีปัญหาเมื่อย้ายรหัสไปยังแอปพลิเคชันเดสก์ท็อป ฉันได้ลองวิธีแก้ปัญหาต่าง ๆ ที่กล่าวมาแล้ว GC.Collect()ถูกนำไปใช้แล้วก่อนหน้านี้สองครั้งตามที่แนะนำ

จากนั้นฉันตัดสินใจว่าฉันจะย้ายรหัสเพื่อเปิด Excel ไปที่คลาสย่อย แทนที่จะเปิดเพียงครั้งเดียวตอนนี้ฉันสร้างวัตถุใหม่ (คลาสฐาน) และเปิด Excel สำหรับทุกรายการและปิดท้าย มีการปรับประสิทธิภาพบางอย่าง แต่ขึ้นอยู่กับการทดสอบหลายกระบวนการของ Excel กำลังปิดโดยไม่มีปัญหา (ในโหมดดีบัก) ดังนั้นไฟล์ชั่วคราวจะถูกลบออก ฉันจะทำการทดสอบต่อไปและเขียนเพิ่มเติมถ้าฉันจะได้รับการปรับปรุง

บรรทัดล่างคือ: คุณต้องตรวจสอบรหัสเริ่มต้นโดยเฉพาะอย่างยิ่งถ้าคุณมีหลายชั้นเรียน ฯลฯ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.