Excel birlikte çalışma nesnelerini düzgün bir şekilde nasıl temizlerim?


747

C # ( ApplicationClass) Excel birlikte çalışma özelliğini kullanıyorum ve nihayet yan tümce aşağıdaki kodu yerleştirdik:

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

Bu tür işler olsa da, Excel.exeExcel'i kapattıktan sonra bile süreç hala arka planda. Yalnızca uygulamam elle kapatıldığında serbest bırakılır.

Neyi yanlış yapıyorum ya da birlikte çalışma nesnelerinin uygun şekilde imha edilmesini sağlayan bir alternatif var mı?


1
Uygulamanızı kapatmadan Excel.exe dosyasını kapatmaya mı çalışıyorsunuz? Sorunuzu tam olarak anladığımdan emin değilim.
Bryant

3
Yönetilmeyen birlikte çalışma nesnelerinin düzgün bir şekilde atıldığından emin olmaya çalışıyorum. Böylece, kullanıcı uygulamadan oluşturduğumuz Excel elektronik tablosunu bitirdiğinde bile asılı Excel işlemleri olmaz.
17'de HAdes

3
XML Excel dosyaları üreterek yapmaya çalışabilirseniz, lütfen VSTO yönetilmeyen / yönetilmeyen Bellek Yönetimini düşünün
Jeremy Thompson

Bu Excel'e güzel bir şekilde mi çevriliyor?
Coops

2
Microsoft'un bu soruna özellikle çözüm sundukları bu destek makalesine bakın (aşağıdaki yanıtların yanı sıra): support.microsoft.com/kb/317109
Arjan

Yanıtlar:


684

Uygulamanız hala COM nesnelerine başvuruları tuttuğu için Excel çıkmıyor.

Sanırım bir COM nesnesinin en az bir üyesini bir değişkene atamadan çağırıyorsunuz.

Benim için bir değişkene atamadan doğrudan kullandığım excelApp.Worksheets nesnesiydi:

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

Dahili olarak C # kodum tarafından yayınlanmadı Worksheets COM nesnesi için bir sarmalayıcı oluşturduğunu bilmiyordum (çünkü farkında değildi çünkü) ve Excel neden kaldırılmadı nedeni oldu.

Ayrıca C # COM nesneleri kullanımı için güzel bir kuralı olan bu sayfada benim sorunumun çözüm buldum :

COM nesneleriyle asla iki nokta kullanmayın.


Yani bu bilgi ile yukarıdakileri yapmanın doğru yolu:

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

MORTEM SONRASI GÜNCELLEME:

Her okuyucunun Hans Passant'ın bu cevabı çok dikkatli bir şekilde okumasını istiyorum, çünkü ben ve diğer birçok geliştiricinin tuzağını açıklıyor. Bu cevabı yıllar önce yazdığımda, hata ayıklayıcının çöp toplayıcısına olan etkisini bilmiyordum ve yanlış sonuçlar çıkardım. Ben tarihin uğruna değiştirilmemiş benim cevap tutmak ancak bu bağlantıyı okumak ve lütfen yok "iki nokta" nin yol: anlama çöp toplama .NET içinde ve Temiz Excel Interop kadar IDisposal ile Nesneleri


16
Sonra Excel'den COM kullanmamanızı ve tüm sorunları kendinizden kurtarmanızı öneririm. Excel 2007 formatları muhteşem Excel'i hiç açmadan kullanılabilir.
user7116

5
"İki noktanın" ne anlama geldiğini anlamadım. Açıklayabilir misin?
A9S6

22
Bu, comObject.Property.PropertiesProperty desenini kullanmamanız gerektiği anlamına gelir (iki noktayı görüyor musunuz?). Bunun yerine bir değişkene comObject.Property atayın ve bu değişkeni kullanın ve atın. Yukarıdaki kuralın daha resmi bir versiyonu olabilir. "Değişkenleri kullanmadan önce com nesnesi ata. Buna başka bir com nesnesinin özellikleri olan com nesneleri de dahildir."
VVS

5
@Nick: Aslında, çöp toplayıcı sizin için yapacağından herhangi bir temizliğe ihtiyacınız yoktur. Yapmanız gereken tek şey, her COM nesnesini kendi değişkenine atamaktır, böylece GC bunu bilir.
VVS

12
@VSS bu kilitler, sarma değişkenleri .net çerçevesi tarafından yapıldığından GC her şeyi temizler. GC'nin temizlenmesi sonsuza kadar sürebilir. Ağır birlikte çalıştıktan sonra GC.Collect çağrısı kötü bir fikir değildir.
CodingBarfield

280

Aslında Excel Uygulama nesnenizi temiz bir şekilde serbest bırakabilirsiniz, ancak dikkat etmeniz gerekir.

Kesinlikle eriştiğiniz her COM nesnesi için adlandırılmış bir referans tutma ve sonra açıkça onu serbest bırakma önerisi Marshal.FinalReleaseComObject()teoride doğrudur, ancak maalesef pratikte yönetilmesi çok zordur. Biri herhangi bir yere kayıyorsa ve "iki nokta" kullanıyorsa veya hücreleri bir for eachdöngü veya benzeri bir komutla yineliyorsa, referanssız COM nesneleriniz olur ve askıda kalma riskiyle karşı karşıya kalırsınız. Bu durumda, kodda nedeni bulmanın hiçbir yolu olmazdı; büyük bir proje için neredeyse imkansız olabilecek bir görevi tüm kodunuzu gözden geçirmeniz ve umarım sebebi bulmanız gerekir.

İyi haber şu ki, kullandığınız her COM nesnesine adlandırılmış bir değişken başvurusu sağlamak zorunda değilsiniz. Bunun yerine, başvuru içermediğiniz tüm (genellikle küçük) nesneleri serbest bırakmak ve sonra adlandırılmış değişken başvurusunu tuttuğunuz nesneleri açıkça serbest bırakmak için GC.Collect()ve sonra çağrı yapın GC.WaitForPendingFinalizers().

Ayrıca adlandırılmış referanslarınızı önem sırasının tersine de bırakmanız gerekir: önce nesneleri, sonra çalışma sayfalarını, çalışma kitaplarını ve son olarak Excel Uygulama nesnenizi aralıklandırın.

Örneğin, adlı bir Range nesnesi değişkenine, adlı xlRngbir Çalışma Sayfası değişkenine, adlı xlSheetbir Çalışma Kitabı değişkenine xlBookve adlı bir Excel Uygulama değişkenine sahip olduğunuzu varsayarak, xlApptemizleme kodunuz aşağıdaki gibi görünebilir:

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

Çoğu kod örneğinde, COM nesnelerini .NET'ten temizlemek için görürsünüz ve GC.Collect()ve GC.WaitForPendingFinalizers()çağrıları aşağıdaki gibi TWICE yapılır:

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

Ancak, nesnelerin tüm grafiğinin sonlandırma kuyruğunda yükseltilmesine neden olan sonlandırıcıları kullanan Office için Visual Studio Araçları (VSTO) kullanmadığınız sürece bu gerekli olmamalıdır. Bu tür nesneler bir sonraki çöp toplama işlemine kadar serbest bırakılmaz . Eğer VSTO kullanmıyorsanız Ancak, aramak gerekir GC.Collect()ve GC.WaitForPendingFinalizers()sadece bir kez.

Açıkça aramanın GC.Collect()hayır-hayır olduğunu biliyorum (ve kesinlikle iki kez yapmak çok acı verici geliyor), ama dürüst olmak gerekirse, bunun hiçbir yolu yok. Normal işlemler sayesinde, başvurmadığınız gizli nesneler oluşturacaksınız, bu nedenle, arama dışında başka bir yolla serbest bırakamazsınız GC.Collect().

Bu karmaşık bir konu, ama gerçekten de hepsi bu kadar. Temizleme yordamınız için bu şablonu oluşturduktan sonra, sarıcıya ihtiyaç duymadan normal olarak kodlayabilirsiniz. :-)

Burada bu konuda bir öğretici var:

VB.Net / COM Interop ile Office Programlarını Otomatikleştirme

VB.NET için yazılmıştır, ancak bundan vazgeçmeyin, prensipler C # ile aynıdır.


3
İlgili bir tartışma ExtremeVBTalk .NET Office Automation forumunda bulunabilir: xtremevbtalk.com/showthread.php?t=303928 .
Mike Rosenblum

2
Ve her şey başarısız olursa, burada açıklandığı gibi Process.Kill () kullanılabilir (son çare olarak): stackoverflow.com/questions/51462/…
Mike Rosenblum

1
Çalışmasına sevindim Richard. :-) Ve burada "iki noktadan" kaçınmanın bir sorunu önlemek için yeterli olmadığı ince bir örnek: stackoverflow.com/questions/4964663/…
Mike Rosenblum

İşte bu konuda Microsoft'un Misha Shneerson'dan 7 Ekim 2010 yorum tarihi içinde bir görüş. ( Blogs.msdn.com/b/csharpfaq/archive/2010/09/28/… )
Mike Rosenblum

Bunun, yaşadığımız kalıcı bir soruna yardımcı olmasını umuyorum. Sonunda bir blok içinde çöp toplama ve bırakma çağrıları yapmak güvenli midir?
Brett Green

213

Önsöz: Cevabım iki çözüm içeriyor, bu yüzden okurken dikkatli olun ve hiçbir şeyi kaçırmayın.

Excel örneğinin nasıl kaldırılacağının farklı yolları ve önerileri vardır, örneğin:

  • HER com nesnesini Marshal.FinalReleaseComObject () ile açıkça serbest bırakmak (örtük olarak oluşturulan com nesnelerini unutmamak). Oluşturulan her com nesnesini serbest bırakmak için, burada belirtilen 2 nokta kuralını kullanabilirsiniz:
    Excel birlikte çalışma nesnelerini düzgün bir şekilde nasıl temizlerim?

  • CLR'yi kullanılmayan com-nesnelerin yayınlanması için GC.Collect () ve GC.WaitForPendingFinalizers () yöntemini çağırmak * (Aslında işe yarar, ayrıntılar için ikinci çözümüme bakın)

  • Com-server-application'un kullanıcının yanıt vermesini bekleyen bir mesaj kutusu gösterip göstermediğini kontrol etmek (Excel'in kapanmasını engelleyebileceğinden emin değilim, ancak birkaç kez duydum)

  • Ana Excel penceresine WM_CLOSE mesajı gönderme

  • Excel ile çalışan işlevi ayrı bir AppDomain içinde yürütme. Bazı kişiler, AppDomain kaldırıldığında Excel örneğinin kapatılacağına inanıyor.

  • Excel ile birlikte çalışma kodumuz başladıktan sonra başlatılan tüm excel örneklerini öldürmek.

FAKAT! Bazen tüm bu seçenekler yardımcı olmaz veya uygun olamaz!

Örneğin, dün (excel ile çalışan) işlevlerimden birinde, işlev sona erdikten sonra Excel'in çalışmaya devam ettiğini öğrendim. Her şeyi denedim! Tüm işlevi 10 kez iyice kontrol ettim ve her şey için Marshal.FinalReleaseComObject () ekledim! Ayrıca GC.Collect () ve GC.WaitForPendingFinalizers () vardı. Gizli mesaj kutularını kontrol ettim. Ana Excel penceresine WM_CLOSE mesajı göndermeye çalıştım. İşlevimi ayrı bir AppDomain'de yürüttüm ve bu etki alanını kaldırdım. Hiçbir şey yardımcı olmadı! Tüm excel örneklerini kapatma seçeneği uygun değildir, çünkü kullanıcı başka bir Excel örneğini el ile başlatırsa, Excel ile de çalışan işlevimin yürütülmesi sırasında, bu örnek de işlevim tarafından kapatılacaktır. Bahse girerim kullanıcı mutlu olmaz! Yani, dürüst olmak gerekirse, bu topal bir seçenektir (suçsuzlar).Çözüm : Excel penceresini ana penceresinden hWnd ile öldürün (bu ilk çözümdür).

İşte basit kod:

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

Gördüğünüz gibi Try-Parse desenine göre iki yöntem sağladım (burada uygun olduğunu düşünüyorum): Süreç öldürülemezse bir yöntem istisnayı atmaz (örneğin süreç artık mevcut değildir) ve İşlem öldürülmediyse başka bir yöntem istisnayı atar. Bu koddaki tek zayıf yer güvenlik izinleridir. Teorik olarak, kullanıcının işlemi öldürme izni olmayabilir, ancak tüm vakaların% 99,99'unda kullanıcının bu tür izinleri vardır. Ayrıca bir konuk hesabıyla test ettim - mükemmel çalışıyor.

Excel ile çalışan kodunuz şöyle görünebilir:

int hWnd = xl.Application.Hwnd;
// ...
// here we try to close Excel as usual, with xl.Quit(),
// Marshal.FinalReleaseComObject(xl) and so on
// ...
TryKillProcessByMainWindowHwnd(hWnd);

İşte bu kadar! Excel sonlandırıldı! :)

Tamam, yazının başında söz verdiğim gibi ikinci çözüme geri dönelim. İkinci çözüm GC.Collect () ve GC.WaitForPendingFinalizers () yöntemini çağırmaktır. Evet, aslında çalışıyorlar, ama burada dikkatli olmalısınız!
Birçok kişi GC.Collect () çağrısının yardımcı olmadığını söylüyor (ve ben dedim). Ancak yardımcı olmamanın nedeni, COM nesnelerine hala başvurular olup olmadığıdır! GC.Collect () 'in yardımcı olmamasının en popüler nedenlerinden biri projeyi Hata Ayıklama modunda çalıştırmaktır. Hata ayıklama modunda artık başvurulmayan nesneler yöntemin sonuna kadar çöp toplanmayacaktır.
Dolayısıyla, GC.Collect () ve GC.WaitForPendingFinalizers () öğesini denediyseniz ve yardımcı olmadıysa, aşağıdakileri yapmaya çalışın:

1) Projenizi Yayın modunda çalıştırmayı deneyin ve Excel'in doğru kapanıp kapanmadığını kontrol edin

2) Excel ile çalışma yöntemini ayrı bir yöntemle sarın. Yani, böyle bir şey yerine:

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

Sen yaz:

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

Şimdi Excel kapanacak =)


19
üzgün iplik zaten o kadar eski ki mükemmel cevabınız çok aşağıda görünüyor, ki ben daha fazla kez upvoted olmamasının tek nedeni olduğunu düşünüyorum ...
chiccodoro

15
İtiraf etmeliyim ki, cevabınızı ilk okuduğumda, bunun dev bir çamur olduğunu düşündüm. Bununla yaklaşık 6 saat güreştikten sonra (her şey serbest bırakıldı, çift noktam yok, vb.), Şimdi cevabın deha olduğunu düşünüyorum.
Mark

3
Bunun için teşekkürler. Bunu bulmadan birkaç gün önce ne olursa olsun kapanmayacak bir Excel ile güreştim. Mükemmel.
Ekim'de BBlake

2
@nightcoder: harika bir cevap, gerçekten ayrıntılı. Hata ayıklama moduna ilişkin yorumlarınız, bunu belirttiğiniz için çok doğru ve önemlidir. Serbest bırakma modunda aynı kod genellikle iyi olabilir. Process.Kill, ancak programınız Excel'de çalışırken, örneğin yönetilen bir COM eklentisi olarak değil, yalnızca otomasyon kullanılırken iyidir.
Mike Rosenblum

2
@DanM: Yaklaşımınız% 100 doğru ve asla başarısız olmayacak. Tüm değişkenlerinizi yöntem için yerel tutarak, tüm başvurulara .NET kodu tarafından etkin bir şekilde erişilemez. Bu, nihayet bölümünüz GC.Collect () öğesini çağırdığında, tüm COM nesneleriniz için sonlandırıcıların kesin olarak çağrılacağı anlamına gelir. Her sonlandırıcı daha sonra sonlandırılan nesne üzerinde Marshal.FinalReleaseComObject öğesini çağırır. Bu nedenle yaklaşımınız basit ve kusursuzdur. Bunu kullanmaktan korkmayın. (Sadece uyarı: VSTO kullanıyorsanız, sizden şüpheliyim, GC.Collect () & GC.WaitForPendingFinalizers TWICE'ı aramanız gerekir.)
Mike Rosenblum

49

GÜNCELLEME : C # kodu eklendi ve Windows İşlerine bağlantı

Ben bazen bu sorunu anlamaya çalışırken geçirdim ve o zaman XtremeVBTalk en aktif ve duyarlıydı. İşte orijinal yazıma bir bağlantı , Uygulamanız çökse bile bir Excel Birlikte Çalışma işlemini temiz bir şekilde kapatma . Aşağıda gönderinin bir özeti ve kod bu gönderiye kopyalanmıştır.

  • İle birlikte çalışma süreci Kapanış Application.Quit()ve Process.Kill()çoğunlukla çalışır, ancak uygulamalar felaket çökerse başarısız olur. Yani uygulama çökerse, Excel işlemi yine de gevşek olarak çalışacaktır.
  • Çözüm, işletim sisteminin Win32 çağrılarını kullanarak Windows İş Nesneleri aracılığıyla işlemlerinizi temizlemesine izin vermektir . Ana uygulamanız öldüğünde, ilişkili işlemler (yani Excel) de sonlandırılır.

Bunu temiz bir çözüm olarak buldum çünkü işletim sistemi gerçek temizlik işlerini yapıyor. Tek yapmanız gereken Excel işlemini kaydetmek .

Windows İş Kodu

Interop işlemlerini kaydetmek için Win32 API Çağrılarını sarar.

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

}

Yapıcı kodu hakkında not

  • Yapıcıda info.LimitFlags = 0x2000;denir. 0x2000bir JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSEnumaralama değeri ve bu değer ile tanımlanır MSDN'den olarak:

İşle ilgili son işlem kapatıldığında işle ilişkili tüm işlemlerin sona ermesine neden olur.

İşlem Kimliğini (PID) almak için Ekstra Win32 API Çağrısı

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

Kodu kullanma

    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
İşlem dışı bir COM sunucusunu (diğer COM istemcilerine hizmet ediyor olabilir) açıkça öldürmek korkunç bir fikirdir. Buna başvuruyorsanız, bunun nedeni COM protokolünüzün bozuk olmasıdır. COM sunucuları normal bir pencereli uygulama olarak ele
alınmamalıdır

38

Bu, üzerinde çalıştığım bir proje için çalıştı:

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

Bir Excel COM nesnesine yapılan her başvuruyu, işiniz bittiğinde null olarak ayarlamanın önemli olduğunu öğrendik . Bu, Hücreleri, E-Tabloları ve her şeyi içeriyordu.


30

Excel ad alanındaki her şeyin serbest bırakılması gerekir. dönem

Yapamazsın:

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

Yapmalısın

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

ardından nesnelerin serbest bırakılması.


3
Örneğin xlRange.Interior.Color nasıl aobut.
17'de HAdes

İç mekanın serbest bırakılması gerekiyor (ad alanında) ... Öte yandan renk (System.Drawing.Color, iirc'den kaynaklanmıyor)
MagicKat

2
Aslında Renk .Net rengi değil Excel rengidir. Sadece bir Long'u geçiyorsun. Ayrıca çalışma kitapları def. serbest bırakılması gerekiyor, çalışma sayfaları ... daha az.
İsimsiz Tip

Bu doğru değil, iki nokta kötü, tek nokta iyi kural saçmalık. Ayrıca, GC'nin işi olan Çalışma Kitaplarını, Çalışma Sayfalarını, Aralıkları serbest bırakmak zorunda değilsiniz. Asla unutmamanız gereken tek şey bir ve tek Excel.Application nesnesinde Quit'i çağırmaktır. Quit'i çağırdıktan sonra Excel.Application nesnesini boş bırakın ve GC.Collect () öğesini çağırın; GC.WaitForPendingFinalizers (); iki defa.
Dietrich Baumgarten

29

İlk olarak - asla aramanıza Marshal.ReleaseComObject(...)veya Marshal.FinalReleaseComObject(...)Excel birlikte çalışmasına gerek kalmaz. Kafa karıştırıcı bir anti-desendir, ancak Microsoft'tan dahil olmak üzere bu konuda .NET'ten el ile COM başvurularını serbest bırakmanız gerektiğini gösteren herhangi bir bilgi yanlıştır. Gerçek şu ki, .NET çalışma zamanı ve çöp toplayıcı COM referanslarını doğru bir şekilde takip eder ve temizler. Kodunuz için bu, üstteki `while (...) döngüsünün tamamını kaldırabileceğiniz anlamına gelir.

İkincisi, işleminiz bittiğinde (Excel işleminin kapanması için) işlem dışı bir COM nesnesine COM başvurularının temizlendiğinden emin olmak istiyorsanız, çöp toplayıcının çalıştığından emin olmanız gerekir. Sen çağrıları ile düzgün bunu GC.Collect()ve GC.WaitForPendingFinalizers(). Bunu iki kez aramak güvenlidir ve döngülerin de kesinlikle temizlenmesini sağlar (bunun gerekli olduğundan emin değilim ve bunu gösteren bir örneği takdir edeceğim).

Üçüncüsü, hata ayıklayıcı altında çalışırken, yerel referanslar yöntemin sonuna kadar yapay olarak canlı tutulacaktır (böylece yerel değişken denetimi çalışır). Dolayısıyla, GC.Collect()aramalar rng.Cellsaynı yöntemdeki gibi nesnelerin temizlenmesi için etkili değildir . GC temizlemesinden COM birlikte çalışma yapan kodu ayrı yöntemlere bölmelisiniz. (Bu, benim için @ nightcoder tarafından gönderilen yanıtın bir bölümünden önemli bir keşifti.)

Böylece genel örüntü şöyle olacaktır:

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 ve Stack Overflow (ve özellikle bu soru!) İle ilgili birçok yayın da dahil olmak üzere, bu konuda birçok yanlış bilgi ve karışıklık var.

Nihayet beni daha yakından bakmaya ve doğru tavsiyeyi bulmaya ikna eden şey, Marshal.ReleaseComObject olarak kabul edildi.


5
BU SAYFADAKİ TÜM CEVAPLAR BU BİRİ DIŞINDA YANLIŞTIR. Çalışıyorlar ama çok fazla çalışıyorlar. IGNORE "2 DOTS" kuralı. Bırakın GC sizin için işinizi yapsın. Net GURU Hans
Passant'ın

1
Govert, bunu yapmanın doğru yolunu bulmak için ne rahatlama. Teşekkür ederim.
Dietrich Baumgarten

18

Ben buldum kutu yardım COM nesneleri için doğru atılması deseni, Marshal.ReleaseComObject kapsam dışına gidince denilen bu ihtiyacı uygulamak belirten yararlı genel bir şablon:

Kullanımı:

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

Şablon:

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

Referans:

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


3
evet bu iyi. Ben FinalReleaseCOMObject kullanılabilir şimdi olsa bu kod güncellenebilir düşünüyorum.
İsimsiz Tip

Her nesne için bir kullanım bloğuna sahip olmak hala sinir bozucu. Alternatif için bkz. Stackoverflow.com/questions/2191489/… .
Henrik

16

Bu sorunun 5 yıldır dünyaya perili olduğuna inanamıyorum .... Bir uygulama oluşturduysanız, bağlantıyı kaldırmadan önce kapatmanız gerekir.

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

kapatırken

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

Bir excel uygulaması yeni, arka planda bir excel programı açar. Bu excel programı doğrudan kontrolünüzün bir parçası olmadığından, bağlantıyı bırakmadan önce bu excel programına son vermek için komut vermeniz gerekir. Bu nedenle, bağlantı serbest bırakılırsa açık kalacaktır!

Herkese iyi programlama ~~


Hayır, özellikle COM uygulamasıyla kullanıcı etkileşimine izin vermek istiyorsanız bunu yapmazsınız (bu durumda Excel - OP, kullanıcı etkileşiminin amaçlanıp amaçlanmadığını belirtmez, ancak kapatmaya referans vermez). Arayanın, örneğin, tek bir dosyayı kapatırken kullanıcının Excel'den çıkabilmesi için gitmesine izin vermeniz yeterlidir.
downwitch

Bu benim için mükemmel çalıştı - ben sadece objExcel.Quit () vardı çöktü, ama aynı zamanda objExcel.Application.Quit () önceden yaptım, temiz kapatıldı.
mcmillab

13

Ortak geliştiriciler, çözümlerinizden hiçbiri işe yaramadı, bu yüzden yeni bir numara uygulamaya karar verdim .

Önce "Hedefimiz nedir?" => "Görev yöneticisinde işimizden sonra excel nesnesini göremiyorum"

Tamam. Meydan okumaya ve yok etmeye başlamalıyız, ancak paralel çalışan diğer Excel işletim sistemlerini yok etmemeyi düşünün.

Bu nedenle, mevcut işlemcilerin listesini alın ve EXCEL işlemlerinin PID'sini getirin, sonra işiniz tamamlandığında, benzersiz bir PID ile işlemler listesinde yeni bir misafirimiz var, sadece bunu bulun ve yok edin.

<excel işiniz sırasında herhangi bir yeni excel sürecinin yeni ve imha olarak algılanacağını unutmayın> <Yeni oluşturulan excel nesnesinin PID'sini yakalamak ve sadece yok etmek daha iyi bir çözümdür>

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

Bu benim sorunumu çözer, umarım sizindir.


1
.... bu biraz balyoz yaklaşımı mı? - benim de kullandığım şeye benziyor :( ama değişmesi gerekiyor.Bu yaklaşımı kullanmayla ilgili sorun, excel'i açarken, bunun çalıştığı makinede, sol çubukta bir uyarı söyleyerek, "Excel yanlış kapatıldı": çok şık değil. Bence bu konudaki önceki önerilerden biri tercih edilir.
whytheq

11

Bu kesinlikle aşırı karmaşık görünüyor. Deneyimlerime göre, Excel'in düzgün bir şekilde kapanmasını sağlamak için sadece üç önemli şey var:

1: oluşturduğunuz excel uygulamasına kalan referans olmadığından emin olun (yine de yalnızca bir tane olmalıdır; bunu ayarlayın null)

2: çağrı GC.Collect()

3: Excel, kullanıcı programı elle kapatarak veya QuitExcel nesnesini çağırdığınızda kapatılmalıdır . ( QuitKullanıcı programı kapatmaya çalışmış gibi çalışacak ve kaydedilmemiş değişiklikler varsa, Excel görünür olmasa bile bir onay iletişim kutusu sunacağını unutmayın. Kullanıcı iptal düğmesine basabilir ve Excel kapatılmayacaktır. )

1'in 2'den önce olması gerekir, ancak 3 her zaman olabilir.

Bunu uygulamanın bir yolu, birlikte çalışma Excel nesnesini kendi sınıfınızla sarmak, yapıcıda birlikte çalışma örneği oluşturmak ve IDisposable'ı Dispose gibi görünerek uygulamaktır.

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

Bu, programınızın şeylerinden excel'i temizleyecektir. Excel kapatıldıktan sonra (kullanıcı tarafından veya sizin aradığınız elle Quit) işlem kaybolur. Program zaten kapatılmışsa, GC.Collect()çağrı sırasında işlem kaybolacaktır .

(Ne kadar önemli olduğundan emin değilim, ancak GC.WaitForPendingFinalizers()çağrıdan sonra bir çağrı isteyebilirsiniz, ancak GC.Collect()Excel işleminden kurtulmak kesinlikle gerekli değildir.)

Bu benim için yıllarca sorunsuz çalıştı. Yine de, bu çalışırken, aslında çalışması için zarifçe kapatmanız gerektiğini unutmayın. Excel temizlenmeden önce programınızı kesintiye uğratırsanız excel.exe işlemlerini biriktirmeye devam edersiniz (genellikle programınız hata ayıklanırken "stop" düğmesine basarak).


1
Bu cevap işe yarar ve kabul edilen cevaptan çok daha uygundur. COM nesneleri ile "iki nokta" hakkında hiçbir endişe, hiçbir faydası Marshal.
andrew.cuthbert

9

Okuma, oluşturma sırasında her nesneye doğrudan referanslar oluştursanız bile, Excel'in kapanma nedenlerine eklemek için 'For' döngüsüdür.

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

Ve başka bir neden, önce bir önceki değeri serbest bırakmadan bir referansı yeniden kullanmak.
Grimfort

Teşekkür ederim. Ayrıca çözümünüzün benim için çalıştığı "her biri için" kullanıyordum.
Chad Braun-Duin

+1 Teşekkürler. Ayrıca, Excel aralığı için bir nesneniz varsa ve nesnenin ömrü boyunca aralığı değiştirmek istiyorsanız, yeniden atamadan önce ReleaseComObject'i kullanmam gerektiğini buldum, bu da kodu biraz düzensiz hale getiriyor!
AjV Jsy

9

Geleneksel olarak VVS'nin cevabında bulunan tavsiyelere uydum . Ancak, bu yanıtı en son seçeneklerle güncel tutmak için, gelecekteki tüm projelerimin "NetOffice" kütüphanesini kullanacağını düşünüyorum.

NetOffice , Office PIA'ların yerine geçer ve tamamen sürümden bağımsızdır. .NET'te Microsoft Office ile çalışırken sık sık bu tür baş ağrılarına neden olan temizlemeyi kaldırabilen bir Yönetilen COM sarmalayıcıları koleksiyonudur.

Bazı temel özellikler:

  • Çoğunlukla sürümden bağımsız (ve sürüme bağlı özellikler belgelenir)
  • Bağımlılık yok
  • PIA yok
  • Kayıt yok
  • VSTO yok

Hiçbir şekilde projeye bağlı değilim; Sadece baş ağrılarındaki kesin azalmayı gerçekten takdir ediyorum.


2
Bu gerçekten cevap olarak işaretlenmelidir. NetOffice tüm bu karmaşıklığı ortadan kaldırır.
C. Augusto Proiete

1
NetOffice bir excel eklenti yazma zaman önemli miktarda kullanıyorum ve mükemmel çalıştı. Dikkate alınması gereken tek şey, kullanılmış nesneleri açıkça atmazsanız, uygulamadan çıktığınızda bunu yapacaktır (çünkü onları yine de izler). NetOffice ile el kararı Yani vb hücre, aralık veya levha gibi her bir Excel nesnesi ile desen "kullanarak" kullanımına daima
Stas İvanov

8

Buradaki kabul edilen cevap doğrudur, ancak sadece "iki nokta" referansından değil, aynı zamanda indeks yoluyla alınan nesnelerden de kaçınılması gerektiğini unutmayın. Ayrıca, bu nesneleri temizlemek için program bitinceye kadar beklemenize de gerek yoktur, mümkün olduğunca onlarla işiniz bittiğinde onları temizleyecek işlevler oluşturmak en iyisidir. Oluşturduğum bir Style nesnesinin bazı özelliklerini atayan bir fonksiyon 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 { } 
}

Bunu xlBorders[Excel.XlBordersIndex.xlEdgeBottom]temizlemek için bir değişkene ayarlamam gerektiğine dikkat edin (Serbest bırakılması gerekmeyen bir numaralandırmaya atıfta bulunan iki noktadan değil, bahsettiğim nesne aslında bir Border nesnesi olduğundan serbest bırakılması gerekir).

Bu tür bir şey, kendilerinden sonra temizlik yapmak için harika bir iş yapan standart uygulamalarda gerçekten gerekli değildir, ancak ASP.NET uygulamalarında, bunlardan birini bile kaçırırsanız, çöp toplayıcısını ne sıklıkta çağırırsanız arayın, Excel hala sunucunuzda çalışıyor.

Bu kodu yazarken Görev Yöneticisi'ni izlerken ayrıntılara ve birçok test yürütmesine çok dikkat etmek gerekir, ancak bunu yapmak, kaçırdığınız bir örneği bulmak için kod sayfalarında umutsuzca arama yapma zahmetinden kurtarır. Bu, her bir döngüde aynı değişken adını kullansa bile, bir nesnenin HER DURUMUNU serbest bırakmanız gereken döngülerde çalışırken özellikle önemlidir.


Inside Release içinde Null (Joe'nun cevabı) olup olmadığını kontrol edin. Bu gereksiz boş istisnaları önleyecektir. Bir çok yöntemi test ettim. Excel'i etkili bir şekilde yayınlamanın tek yolu budur.
Gerhard Powell

8

Denedikten sonra

  1. COM nesnelerini ters sırada serbest bırakma
  2. Ekle GC.Collect()ve GC.WaitForPendingFinalizers()sonunda iki kez
  3. En fazla iki nokta
  4. Çalışma kitabını kapat ve uygulamadan çık
  5. Serbest bırakma modunda çalıştır

benim için çalışan son çözüm,

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

aşağıdaki gibi bir paketleyiciye işlevin sonuna ekledik:

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

ComObjects, sadece Quit () - Close () ve FinalReleaseComObject geçersiz kılmak zorunda değildi bulundu. Çalışmak için sonunda eksik olan her şeyin bu olduğuna inanamıyorum. Harika!
Carol

7

Bunu tam olarak takip ettim ... Ama hala 1000 kez 1'e rastladım. Kim bilir neden. Çekiç çıkma zamanı ...

Excel Application sınıfı somutlaştırıldıktan hemen sonra, yeni oluşturulan Excel sürecini ele alıyorum.

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

Sonra yukarıdaki COM temizliği yaptıktan sonra, işlemin çalışmadığından emin olun. Hala çalışıyorsa, öldür!

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

7

Pro ° º¤ø „¸ Excel proc çekin ve sakız çiğneyin ¸„ ø¤º ° ¨

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'in de koştuğunuz kültüre karşı çok hassas olduğunu bilmeniz gerekir.

Excel işlevlerini çağırmadan önce kültürü EN-US olarak ayarlamanız gerektiğini görebilirsiniz. Bu, tüm işlevler için değil, bazıları için geçerlidir.

    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;

Bu, VSTO kullanıyor olsanız bile geçerlidir.

Ayrıntılar için: http://support.microsoft.com/default.aspx?scid=kb;en-us;Q320369


6

COM referanslarının sızmasını önlemek için "COM nesneleriyle asla iki nokta kullanmayın" büyük bir kuraldır, ancak Excel PIA sızıntıya ilk bakışta göründüğünden daha fazla yol açabilir.

Bu yollardan biri, Excel nesne modelinin COM nesnelerinden herhangi birinin maruz kaldığı herhangi bir olaya abone olmaktır.

Örneğin, Application sınıfının WorkbookOpen olayına abone olmak.

COM olayları hakkında bazı teoriler

COM sınıfları, geri arama arabirimleri aracılığıyla bir grup olayı ortaya çıkarır. Olaylara abone olmak için, istemci kodu yalnızca geri arama arabirimini uygulayan bir nesneyi kaydedebilir ve COM sınıfı, belirli olaylara yanıt olarak yöntemlerini çağırır. Geri arama arabirimi bir COM arabirimi olduğundan, olay işleyicilerinin herhangi biri için aldığı herhangi bir COM nesnesinin (parametre olarak) referans sayısını azaltmak, uygulayıcı nesnenin görevidir.

Excel PIA COM Olaylarını Gösterme

Excel PIA, Excel Uygulama sınıfının COM olaylarını geleneksel .NET olayları olarak sunar. İstemci kod abone zaman bir .NET olay ( 'a' vurgu), PIA yaratan bir Excel ile geri arama arayüzü ve kayıtlarını uygulamadan bir sınıfın örneğini.

Bu nedenle, .NET kodundan farklı abonelik isteklerine yanıt olarak bir dizi geri arama nesnesi Excel'e kaydedilir. Etkinlik aboneliği başına bir geri arama nesnesi.

Olay işleme için geri arama arabirimi, PIA'nın her .NET olay aboneliği isteği için tüm arabirim olaylarına abone olması gerektiği anlamına gelir. Seçemez ve seçemez. Bir olay geri araması alındığında, geri arama nesnesi, ilişkili .NET olay işleyicisinin geçerli olayla ilgilenip ilgilenmediğini denetler ve daha sonra işleyiciyi çağırır veya geri aramayı sessizce yok sayar.

COM örneği referans sayıları üzerindeki etkisi

Tüm bu geri arama nesneleri, geri arama yöntemlerinden herhangi biri için (sessizce yok sayılanlar için bile) aldıkları COM nesnelerinin (parametre olarak) referans sayısını azaltmaz. COM nesnelerini serbest bırakmak için yalnızca CLR çöp toplayıcısına güvenirler .

GC çalışması belirleyici olmadığından, bu, Excel işleminin istenenden daha uzun süre beklemesine yol açabilir ve bir 'bellek sızıntısı' izlenimi yaratabilir.

Çözüm

Şu andaki tek çözüm, PIA'nın COM sınıfı için olay sağlayıcısından kaçınmak ve COM nesnelerini belirleyici olarak serbest bırakan kendi olay sağlayıcınızı yazmaktır.

Application sınıfı için bu, AppEvents arabirimini uygulayıp ardından uygulamayı IConnectionPointContainer arabirimini kullanarak Excel'e kaydederek yapılabilir . Application sınıfı (ve bu nedenle, geri çağrı mekanizması kullanarak olayları açığa çıkaran tüm COM nesneleri) IConnectionPointContainer arabirimini uygular.


4

Diğerlerinin işaret ettiği gibi, kullandığınız her Excel nesnesi için açık bir başvuru oluşturmanız ve bu KB makalesinde açıklandığı gibi bu başvuruda Marshal.ReleaseComObject öğesini çağırmanız gerekir . Ayrıca, bir istisna atıldığında bile ReleaseComObject öğesinin her zaman çağrıldığından emin olmak için try / nihayet kullanmanız gerekir. Yani yerine:

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

şunun gibi bir şey yapmanız gerekir:

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

Excel'in kapanmasını istiyorsanız, Application nesnesini bırakmadan önce Application.Quit'i de çağırmanız gerekir.

Gördüğünüz gibi, orta derecede karmaşık bir şey yapmaya çalıştığınızda bu hızla son derece hantal hale gelir. Başarıyla Excel nesne modelinin birkaç basit manipülasyonunu saran basit bir sarmalayıcı sınıfı ile .NET uygulamaları geliştirdim (bir çalışma kitabını açın, bir Range'e yazın, çalışma kitabını kaydedin / kapatın vb.). Sarıcı sınıfı IDisposable'ı uygular, Marshal.ReleaseComObject'i kullandığı her nesneye dikkatlice uygular ve herhangi bir Excel nesnesini uygulamanın geri kalanına genel olarak göstermez.

Ancak bu yaklaşım daha karmaşık gereksinimler için iyi ölçeklenmemektedir.

Bu .NET COM birlikte çalışma büyük bir eksikliğidir. Daha karmaşık senaryolar için, ciddi bir şekilde bir ActiveX DLL VB6 veya Office gibi dışarıdan COM nesneleri ile tüm etkileşimi için temsilci başka bir yönetilmeyen dilde yazmayı düşünün. Daha sonra bu ActiveX DLL dosyasını .NET uygulamanızdan başvurabilirsiniz ve yalnızca bu başvuruyu serbest bırakmanız gerekeceğinden işler çok daha kolay olacaktır.


3

Yukarıdaki tüm öğeler çalışmadığında, Excel'e sayfalarını kapatması için biraz zaman vermeyi deneyin:

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

...
FinalReleaseComObject(app);

2
Keyfi beklemelerden nefret ediyorum. Ancak +1, çünkü çalışma kitaplarının kapanmasını beklemek zorundasınız. Bir alternatif, çalışma kitapları koleksiyonunu bir döngüde yoklamak ve döngü zaman aşımı olarak keyfi beklemeyi kullanmaktır.
dFlat

3

Excel ile ilgili tüm nesneleri serbest bıraktığınızdan emin olun!

Birkaç yol deneyerek birkaç saat geçirdim. Hepsi harika fikirler ama nihayetinde hatamı buldum: Tüm nesneleri serbest bırakmazsanız, yukarıdaki yollardan hiçbiri benim durumumda beğenmenize yardımcı olamaz . Birinci aralık dahil tüm nesneleri serbest bıraktığınızdan emin olun!

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

Seçenekler burada bir arada .


Çok iyi bir nokta! Office 2007 kullandığım için benim adıma bazı temizlik işlemleri yapıldığını düşünüyorum. Ben daha fazla tavsiye kullandık ama burada önerdiğim gibi değişkenleri saklamamış ve EXCEL.EXE çıkıyor ama ben sadece şanslı olabilirim ve daha fazla sorun varsa kesinlikle benim kodun bu bölümüne bakacağız =)
Coops

3

COM nesnelerini serbest bırakma hakkında harika bir makale 2.5 COM Nesnelerini Serbest Bırakma (MSDN).

Savunacağım yöntem, yerel olmayan değişkenler ise Excel.Interop başvurularınızı null yapmak ve sonra çağrı GC.Collect()veGC.WaitForPendingFinalizers() iki kez . Yerel olarak kapsam dahilindeki Interop değişkenleri otomatik olarak ele alınacaktır.

Bu, her COM nesnesi için adlandırılmış bir başvuru tutma gereğini ortadan kaldırır .

Makaleden alınan bir örnek:

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

Bu kelimeler doğrudan makaleden:

Neredeyse tüm durumlarda, RCW referansını iptal etmek ve bir çöp toplama işlemini zorlamak düzgün bir şekilde temizlenir. GC.WaitForPendingFinalizers'ı da çağırırsanız, çöp toplama işlemi yapabildiğiniz kadar belirleyici olacaktır. Yani, ikinci çağrıdan WaitForPendingFinalizers'a dönüşte nesnenin tam olarak ne zaman temizlendiğinden emin olacaksınız. Alternatif olarak Marshal.ReleaseComObject kullanabilirsiniz. Ancak, bu yöntemi kullanmanızın pek olası olmadığını unutmayın.


2

İki nokta kuralı benim için çalışmadı. Benim durumumda, kaynaklarımı temizlemek için bir yöntem oluşturdum:

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

2

Çözümüm

[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 birlikte çalışma uygulamalarını kullanırken çok dikkatli olmalısınız. Tüm çözümleri denedikten sonra hala bir çok "WinWord" işlemi sunucuda (2000'den fazla kullanıcı ile) açık bırakıldı.

Saatlerce sorun üzerinde çalıştıktan sonra, Word.ApplicationClass.Document.Open()aynı anda farklı iş parçacıklarını kullanarak birkaç belge daha fazla açarsanız , IIS çalışan işlemi (w3wp.exe) tüm WinWord işlemleri açık bırakarak çökeceğini fark ettim !

Bu nedenle, bu soruna mutlak bir çözüm yoktur, ancak Office Open XML geliştirme gibi diğer yöntemlere geçme .


1

Kabul edilen cevap benim için işe yaramadı. Yıkıcıdaki aşağıdaki kod işi yaptı.

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

Şu anda Office otomasyonu üzerinde çalışıyorum ve benim için her zaman işe yarayan bir çözümle karşılaştım. Basittir ve herhangi bir işlemi öldürmeyi içermez.

Görünüşe göre, sadece mevcut etkin süreçler arasında döngü yapılarak ve herhangi bir şekilde açık bir Excel işlemine 'erişerek', Excel'in herhangi bir başıboş asılı örneği kaldırılacak gibi görünüyor. Aşağıdaki kod, adın 'Excel' olduğu işlemleri denetler ve işlemin MainWindowTitle özelliğini bir dizeye yazar. Süreçle olan bu 'etkileşim', Windows'u yakalayıp Excel'in donmuş örneğini iptal ediyor gibi görünüyor.

Ben bırakma olayı eklenti hemen önce, boşaltma olayı ateş gibi aşağıdaki yöntemi çalıştırın. Excel'in asılı örneklerini her seferinde kaldırır. Dürüst olmak gerekirse, bunun neden işe yaradığından tam olarak emin değilim, ama benim için iyi çalışıyor ve çift nokta, Marshal.ReleaseComObject veya öldürme işlemleri hakkında endişelenmeden herhangi bir Excel uygulamasının sonuna yerleştirilebilir. Bunun neden etkili olduğuna dair herhangi bir öneriyle çok ilgilenirim.

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

Bence bunların bazıları, çerçevenin Office uygulamalarını işleme biçimidir, ancak yanılmış olabilirim. Bazı günlerde, bazı uygulamalar işlemleri derhal temizler, diğer günlerde uygulama kapanana kadar bekler. Genel olarak, ayrıntılara dikkat etmeyi bıraktım ve sadece günün sonunda yüzen ekstra işlem olmadığından emin olun.

Ayrıca, ve belki de işleri basitleştirmekteyim, ama bence ...

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

Daha önce söylediğim gibi, Excel işleminin ne zaman ortaya çıktığı veya kaybolduğu ayrıntılarına dikkat etme eğiliminde değilim, ancak bu genellikle benim için işe yarıyor. Ayrıca Excel süreçlerini minimum süre dışında bir şey tutmak istemiyorum, ama muhtemelen sadece paranoyak oluyorum.


1

Bazılarının muhtemelen daha önce yazdığı gibi, Excel'i (nesneyi) nasıl kapattığınız önemli değildir ; nasıl açacağınız ve projenin türüne göre de önemlidir .

Bir WPF uygulamasında, temelde aynı kod çok az sorunla veya çok az sorunla çalışır.

Aynı Excel dosyasının farklı parametre değeri için birkaç kez işlendiği bir proje var - örneğin genel bir liste içindeki değerlere dayalı olarak ayrıştırma.

Excel ile ilgili tüm işlevleri temel sınıfa ve ayrıştırıcıyı bir alt sınıfa koydum (farklı ayrıştırıcılar ortak Excel işlevlerini kullanır). Genel bir listedeki her öğe için Excel'in tekrar açılıp kapanmasını istemedim, bu yüzden temel sınıfta yalnızca bir kez açtım ve alt sınıfta kapattım. Kodu bir masaüstü uygulamasına taşırken sorun yaşadım. Yukarıda belirtilen çözümlerin çoğunu denedim. GC.Collect()daha önce önerildiği gibi iki kez uygulanmıştı.

Sonra ben bir alt sınıfa Excel açmak için kod taşımak karar verdim. Sadece bir kez açmak yerine, şimdi yeni bir nesne (temel sınıf) oluşturuyorum ve her öğe için Excel'i açıp sonunda kapatıyorum. Bazı performans cezaları vardır, ancak çeşitli testlere dayanarak Excel işlemleri sorunsuz bir şekilde kapanır (hata ayıklama modunda), bu nedenle geçici dosyalar da kaldırılır. Bazı güncellemeler alırsam test etmeye devam edeceğim ve biraz daha yazacağım.

Sonuç olarak: Başlatma kodunu, özellikle de çok sayıda sınıfınız vb. Varsa kontrol etmelisiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.