ShellWindows和OutOfMemoryException?

时间:2013-05-23 12:34:30

标签: c# memory-leaks out-of-memory

我有一种在Internet Explorer中打开URL的方法。如果已打开Internet Explorer,则应在新选项卡中打开。如果没有,它应该打开一个新的Internet Explorer。

我的代码:

    public static void OpenURL(string Url)
    {
        bool already_navigated = false;

        ShellWindows instances = new ShellWindows();

        //Check if there is an Internet Explorer
        if (instances.Count > 0)
        {
            foreach (InternetExplorer ie in instances)
            {
                if (ie.Name == "Windows Internet Explorer")
                {
                    if (!already_navigated)
                    {
                        //Navigate and open in New Tab
                        already_navigated = true;
                        ie.Navigate(Url, 0x10000);

                        //Bring window to front
                        IntPtr hwnd = (IntPtr)ie.HWND;
                        WindowHandler.Window w = new WindowHandler.Window(hwnd, "Internet Explorer");
                        w.Minimize();
                        w.Restore();
                    }
                }
            }
        }
        //No internet explorer found!
        if (!already_navigated)
        {
            //Start new Internet Explorer
            Process proc = Process.Start("IExplore.exe", Url);
        }
    }

这很棒!但是当我调用这段代码时,我的记忆会在无限循环中增加,直到我得到 OutOfMemoryException ......

经过一些试验和错误后,我发现此代码会引发异常:

    public static void OpenURL(string Url)
    {
        bool already_navigated = false;

        ShellWindows instances = new ShellWindows();
        /*
        //Check if there is an Internet Explorer
        if (instances.Count > 0)
        {
            foreach (InternetExplorer ie in instances)
            {
                if (ie.Name == "Windows Internet Explorer")
                {
                    if (!already_navigated)
                    {
                        //Navigate and open in New Tab
                        already_navigated = true;
                        ie.Navigate(Url, 0x10000);

                        //Bring window to front
                        IntPtr hwnd = (IntPtr)ie.HWND;
                        WindowHandler.Window w = new WindowHandler.Window(hwnd, "Internet Explorer");
                        w.Minimize();
                        w.Restore();
                    }
                }
            }
        }
        //No internet explorer found!
        if (!already_navigated)
        {
            //Start new Internet Explorer
            Process proc = Process.Start("IExplore.exe", Url);
        }*/
    }

此代码不包含

    public static void OpenURL(string Url)
    {
        bool already_navigated = false;
        /*
        ShellWindows instances = new ShellWindows();

        //Check if there is an Internet Explorer
        if (instances.Count > 0)
        {
            foreach (InternetExplorer ie in instances)
            {
                if (ie.Name == "Windows Internet Explorer")
                {
                    if (!already_navigated)
                    {
                        //Navigate and open in New Tab
                        already_navigated = true;
                        ie.Navigate(Url, 0x10000);

                        //Bring window to front
                        IntPtr hwnd = (IntPtr)ie.HWND;
                        WindowHandler.Window w = new WindowHandler.Window(hwnd, "Internet Explorer");
                        w.Minimize();
                        w.Restore();
                    }
                }
            }
        }*/
        //No internet explorer found!
        if (!already_navigated)
        {
            //Start new Internet Explorer
            Process proc = Process.Start("IExplore.exe", Url);
        }
    }

这只留下一个结论,问题在于:

 ShellWindows instances = new ShellWindows();

但是经过一些谷歌搜索,我找不到有类似问题的人。所以我不确定我做错了什么,或者我对这个问题是否正确。

有谁知道发生了什么事?

2 个答案:

答案 0 :(得分:2)

ShellWindows是一个COM对象。你依靠垃圾收集器来释放RCWs(Runtime Callable Wrappers),由终结器处理。

但是当你的代码只调用OpenUrl()但没有做任何其他事情时,这将不会很好。垃圾收集器仅在分配托管对象时运行。如果你不这样做,并且你发布的代码没有做太多的事情,那么你将面临用尽非托管内存的重大风险,即COM对象所使用的内存。 / p>

该条件易于诊断,请使用Perfmon.exe并查看.NET CLR Memory的性能计数器。 “Gen 0 Collections”计数器显示gen 0垃圾收集的执行频率。如果该代码在代码运行时没有经常更改,则可能会出现OOM kaboom。

这就是为什么GC.Collect()存在,计算你创建的“ie”实例的数量,并以某种幻数调用Collect。 Marshal.ReleaseComObject()也可以帮助,通常更麻烦然后它值得,但应该在这里没有麻烦。并且在循环中使用 break

ShellWindows的另一个方面是它是一个公寓线程COM对象。一个昂贵的词意味着它不是线程安全的。这在COM中很重要,它代表对象处理线程安全。如果此代码不在Winforms或WPF应用程序的主线程上运行,您将很容易遇到麻烦。像控制台模式应用程序或服务。或者,如果您在工作线程上运行此代码。

对于公寓线程COM对象来说,这不是一个幸福的家,它需要一个STA线程来满足线程安全要求。 COM将负责这一点,它将创建一个 new 线程,为对象提供一个安全的家。当您创建大量长寿的对象时,这可能会失控。易于诊断,打开非托管调试,并密切关注Debug + Windows + Threads窗口。我们不知道你的其余代码在做什么,但是当代码运行了一段时间后,看到数百个线程就会出现问题。每个线程为其堆栈吞吐一兆字节的虚拟内存,耗费32位进程耗费不超过几万。通过在Main()方法上应用[STAThread]属性或在启动它之前调用Thread上的SetApartmentState()来修复它。

答案 1 :(得分:1)

此代码应与您合作:

public static void OpenURL(string Url)
{
    var t = Type.GetTypeFromProgID("Shell.Application");
    dynamic o = Activator.CreateInstance(t);
    try
    {
        var instances = o.Windows();

        // Check if there is an Internet Explorer
        if (instances.Count > 0)
        {
            for (int i = 0; i < instances.Count; i++)
            {
                var ie = instances.Item(i);
                if (ie == null) continue;

                var path = System.IO.Path.GetFileName((string)ie.FullName);
                if (path.ToLower() == "iexplore.exe")
                {
                    //Navigate and open in New Tab
                    ie.Navigate(Url, 0x10000);
                    return;
                }
            }
        }
    }
    finally
    {
        Marshal.FinalReleaseComObject(o);
    }

    //No internet explorer found. Start a new onr
    Process.Start("IExplore.exe", Url);
}