【问题标题】:ShellWindows and OutOfMemoryException?ShellWindows 和 OutOfMemoryException?
【发布时间】:2013-05-18 19:45:28
【问题描述】:

我有一种在 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();

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

有人知道怎么回事吗?

【问题讨论】:

  • 我打赌是内存泄漏。在 C# 中听起来很奇怪,但是是的,你为什么不在不同点打破它,看看哪个喜欢增加内存的使用? Memory Leak is possible
  • @AnnArbor87 我无法真正调试,因为目前我无法在用于开发的机器上测试代码。我只能通过更改代码并观察发生的情况进行调试。
  • 尝试注释掉代码的其他部分,例如内部 IF (!already_navigated) 并发布发生的情况
  • 我认为你遇到的问题是因为 IE 对象。你关闭实例吗?你想达到什么目标?
  • 这并没有什么不同。只有当我删除 ShellWindows 行时,它才不会导致内存泄漏。

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


【解决方案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);
}

【讨论】:

  • 当我试图实现你的代码时,我遇到了两个错误。第一个声明为了你使用“使用”ShellWindows 必须可转换为 System.IDisposable。第二个错误是我无法将 [] 索引应用于 ShellWindows 的表达式。
  • 我很好奇在每次运行应用程序期间如何调用 OpenURL() 以及调用多少次?您的应用程序是控制台应用程序、Windows(UI 桌面)还是什么?
  • 尝试删除第一个 using,保留 foreach,但应用其余推荐
  • 请尝试我刚刚添加的代码。正如你所料,它在我的机器上运行。
  • 我只调用它一次,然后它就开始变香蕉了。我只使用 .NET 4.0,但我发现您的回答非常有用。
【解决方案2】:

ShellWindows 是一个 COM 对象。您依靠垃圾收集器来释放 RCW(运行时可调用包装器),并由它们的终结器处理。

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

这种情况很容易诊断,使用 Perfmon.exe 并查看 .NET CLR 内存的性能计数器。 “Gen 0 Collections”计数器显示执行 gen 0 垃圾收集的频率。如果在您的代码运行时该计数器没有足够频繁地更改,则可能会出现 OOM 卡轰。

这就是 GC.Collect() 存在的原因,计算您创建的“ie”实例的数量并以某种幻数调用 Collect。 Marshal.ReleaseComObject() 也可以提供帮助,通常比它值得的麻烦更多,但在这里应该毫无问题地运行。并且在你的循环中使用 break

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

对于单元线程 COM 对象来说,这不是一个幸福的家,它需要一个 STA 线程来满足线程安全要求。 COM 会处理这个问题,它会创建一个 new 线程给对象一个安全的家。当您创建大量寿命过长的对象时,这可能会失控。也易于诊断,打开非托管调试并密切关注 Debug + Windows + Threads 窗口。我们不知道您的其余代码在做什么,但是当代码运行一段时间后看到那里有数百个线程意味着厄运。每个线程为其堆栈占用一兆字节的虚拟内存,耗尽一个 32 位进程并不需要超过几千块。通过在 Main() 方法上应用 [STAThread] 属性或在启动线程之前调用线程上的 SetApartmentState() 来解决此问题。

【讨论】:

  • 谢谢,我搞定了!我所做的是在 OpenURL 中创建一个新线程,启动它并在 STA 中加入它。这似乎可以很好地处理所有事情!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-23
  • 2011-07-14
相关资源
最近更新 更多