【问题标题】:WPF Memory not released when called from ActiveX从 ActiveX 调用时未释放 WPF 内存
【发布时间】:2016-04-22 05:52:30
【问题描述】:

我有一个在 UI 中填充 1000 个 ComboBox 的类,代码是

namespace ActiveXMemory
{
 /// <summary>
 /// Interaction logic for MainWindow.xaml
 /// </summary>
 public partial class MainWindow : Window
 {
    public MainWindow()
    {
        InitializeComponent();
        PopulateControls();
    }
    public void PopulateControls() {
        var newGrid = new Grid();
        for (var i = 0; i < 1000; i++)
        {
            ComboBox newcombo = new ComboBox();
            newGrid.Children.Add(newcombo);
        }
        this.Content = newGrid;
    }

    public bool OpenWindow(bool isRunning)
    {
        Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
        {
            try
            {
                this.ShowDialog();
            }
            catch (Exception exp)
            {
                Console.WriteLine(exp.Message);
            }
        }));
        return true;
    }

    public bool CloseWindow()
    {
        Dispatcher.Invoke(DispatcherPriority.Render, new Action(() =>
        {
            try
            {
                this.Close();
            }
            catch (Exception exp)
            {
                //Console.WriteLine(exp.Message);
            }
        }));

        return true;
    }

    private void Window_Closed(object sender, EventArgs e)
    {
        var grid = Content as Grid;

        var children = grid.Children;

        while (children.Count > 0)
        {
            var child = children[0];
            grid.Children.Remove(child);
            child = null;
        }
        grid = null;
    }

 }
}

我为该类创建了一个 ActiveX 库,以便作为 ActiveX 访问,

namespace ActiveXLibrary
{

[ComVisible(true)]
[Guid("EF2EAD91-68A8-420D-B5C9-E30A6F510BDE")]
public interface IActiveXLib
{
    [DispId(1)]
    bool Initialize();
    [DispId(2)]
    bool CloseActiveX();
}

[ComVisible(true)]
[Guid("9DACD44F-0237-4F44-BCB9-0E6B729915D6"),
ClassInterface(ClassInterfaceType.None)]
[ProgId("Samp")]
public class ActiveXLib : IActiveXLib
{
    MainWindow form;
    Thread thr;
    public bool Initialize()
    {
        int i = 0;
        try
        {
            ThreadStart exeFunc = new ThreadStart(() =>
            {
                Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Send, new Action(() =>
                {
                    start();
                }));
            });
            thr = new Thread(exeFunc);
            thr.SetApartmentState(ApartmentState.STA);
            thr.Start();

            while (form == null)
            {
                i++;
                //Console.WriteLine("form Null");
                System.Threading.Thread.Sleep(1000);
                if (i > 30)
                    break;
            }

            return true;
        }
        catch (Exception exp)
        {
            Console.WriteLine("[Initialize]" + exp.Message);
            return false;
        }
    }

    public bool CloseActiveX()
    {
        bool success = false;
        try
        {

            success = form.CloseWindow();
            form = null;
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            GC.WaitForPendingFinalizers();
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            return success;
        }
        catch (Exception exp)
        {
            Console.WriteLine("[CloseActiveX]" + exp.Message);
            return false;
        }
    }

    private void start()
    {
        try
        {
            Console.WriteLine("start() - new MainWindow()");
            form = new MainWindow();
            Console.WriteLine("OpenWindow()");
            form.OpenWindow(true);
        }
        catch (Exception exp)
        {
            MessageBox.Show(exp.Message + "\nPossible Reasons: " + exp.Source + "\n" + exp.InnerException, "Error");
        }
    }
}

}

然后我创建了一个演示项目

  ActiveXLib activeXRef;
    public MainWindow()
    {
        InitializeComponent();
    }

    private void startButton_Click(object sender, RoutedEventArgs e)
    {
        activeXRef = new ActiveXLib();
        activeXRef.Initialize();

    }

    private void stopButton_Click(object sender, RoutedEventArgs e)
    {
        activeXRef.CloseActiveX();
        GC.Collect();
    }

这里的问题是每次我点击开始和停止按钮时,内存不断增加,应用程序第一次启动创建的内存(17MB)没有被释放,即使我删除了 ComboBoxes 并设置了网格内容为空。甚至 GC.Collect() 也没有效果,因为它只会将垃圾添加到队列中。有什么解决办法吗?

我也尝试过像link那样刷新内存,但即使这样,内存仍然保留。(因为我不确定当前进程的句柄是否来自演示项目/来自ActiveXMemory)

编辑

我加入了这条线

 this.Dispatcher.Invoke(DispatcherPriority.Render, GCDelegate);

到 Window_Closed 事件

现在内存被清除了

但我现在面临的一个问题是,如果我尝试立即关闭(调用 CloseActiveX()),那么这不会发生。(即)

    private void startButton_Click(object sender, RoutedEventArgs e)
    {
        activeXRef = new ActiveXLib();
        activeXRef.Initialize();
        activeXRef.CloseActiveX();
        activeXRef = null;
        GC.Collect();

    }

上面的代码仍然有内存锁定,我不明白其中的区别,它只是另一个事件,有人对此有任何想法吗?

【问题讨论】:

  • 在创建新对象之前,您是否尝试过执行 Marshal.ReleaseComObject(activeXRef)(当然,如果 activeXRef 不为空)。
  • 我试过了,得到 ArgumentException "{"对象的类型必须是 __ComObject 或派生自 __ComObject。\r\n参数名称:o"}"
  • 哦,是的,当然,抱歉,我忽略了您的代码,并认为 IActiveXLib 不是 .net。您可以使用 WinDbg 分析您的程序,这很容易理解发生了什么:blogs.msdn.microsoft.com/johan/2007/01/11/…blogs.msdn.microsoft.com/tess/2008/02/20/…
  • 我已经尝试了内存分析器,看到物理内存中仍然存在什么,但我不知道如何修复它

标签: c# wpf memory-leaks activex


【解决方案1】:

我想我得到了解决这个问题的方法,虽然我不知道你的 MainWindow.Open/CloseWindow() 方法是什么样的。使用线程当然是问题的一部分,您必须做一件不直观的事情来防止泄漏具有线程关联性的内部 WPF 管道对象。

必须关闭线程的调度程序。当 UI 线程终止时,通常只会在 WPF 应用程序中发生一次。调度程序进行调度也很重要,WPF 严重依赖它来确保“弱事件”实际上是弱的。

不泄漏的 MainWindow 示例实现:

public class MainWindow : Window {
    public void OpenWindow(bool noidea) {
        this.ShowDialog();

    }
    public bool CloseWindow() {
        this.Dispatcher.Invoke(() => {
            this.Dispatcher.InvokeShutdown();   // <== Important!
        });
        return true;
    }
}

其中 ShowDialog() 确保调度程序进行调度,而 InvokeShutdown() 确保发生清理。通过使用 10 毫秒 DispatcherTimer 而不是按钮进行测试,因此这种情况发生率非常高。我可以删除 GC.Collect() 调用并且内存使用稳定。

【讨论】:

  • 是的,这实际上让它工作了 this.Dispatcher.Invoke(DispatcherPriority.Render, GCDelegate);this.Dispatcher.Invoke(() => { this.Dispatcher.InvokeShutdown(); //
【解决方案2】:

目前我对 ActiveX 部分没有任何评论(将仔细检查并让您知道),但我可以建议一些与 WPF 代码相关的内容。

这应该使组合项目一次性

public class DispCombo : ComboBox, IDisposable
{
    public void Dispose()
    {
        GC.SuppressFinalize(this);
    }
}
public class DispGrid : Grid, IDisposable
{
    public void Dispose()
    {
        GC.SuppressFinalize(this);
    }
}

您将填充一次性控件

public void PopulateControls()
{
    var newGrid = new DispGrid();
    for (var i = 0; i < 1000; i++)
    {
        DispCombo newcombo = new DispCombo();
        newGrid.Children.Add(newcombo);
    }
    this.Content = newGrid;
}

因此关闭阶段将变为(处置而不是设置为空)

private void Window_Closed(object sender, EventArgs e)
{
    var grid = Content as DispGrid;

    var children = grid.Children;

    while (children.Count > 0)
    {
        var child = children[0] as DispCombo;
        grid.Children.Remove(child);
        child.Dispose();
    }
    grid.Dispose();

}

也就是说,现在您可以将 WPF 视图包装在 using 子句中

public class MainLibrary
    {
        public bool OpenWindow() //found no ref to bool isRunning
        {

            using (var mw = new MainWindow())
            {
                mw.ShowDialog();
                mw.Close();
            }

            return true;
        }

        public bool CloseWindow()
        {
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            GC.WaitForPendingFinalizers();
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            return true;
        }
    }

您的垃圾收集即将结束。 这些是我在这些更改后的内存快照,(1)在开始时拍摄,然后(2)在OpenWindow 中的主要ShowDialog 之后拍摄,最后是(3)在CloseWindow 之后拍摄

编辑

最后,如果你需要启动一个Task,调用Dispatcher,这里就是函数

public bool CloseWindow()
        {
            winClose = () =>
            {
                mw.Dispatcher.Invoke(() =>
                   {
                       mw.Close();
                       mw.Dispose();
                   });
            };
            cleanProc = () =>
            {
                mw.Dispatcher.Invoke(() =>
                {
                    GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
                    GC.WaitForPendingFinalizers();
                    GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
                });
            };
            Task.Run(() =>
           {
               winClose.Invoke();
           }).ContinueWith(x =>

               {
                   cleanProc.Invoke();
               });

            return true;
        }

public bool OpenWindow() 
        {
            mw = new MainWindow();
            mw.Show();
            return true;
        }

【讨论】:

  • 感谢您的回答。实际上,我需要 MainWindow 可以从 Demo 项目中关闭,但如果我显示没有调度程序的对话框,则必须由用户事件显式关闭。此外,ActiveX 库中的“表单”对象必须可用作实例,以便在 MainWindow 中进行任何其他更改
  • 当然,你是对的。这就是我添加编辑的原因。抱歉不清楚。我在另一个视图中尝试了一个 wpf 按钮(链接到我的库 CloseWindow),我认为它是可关闭的。如果我还缺少什么,请告诉我。再次感谢。
  • 是的,这可行,但是 ,mw.Show() 会导致用户事件(如点击/键盘事件)无法访问窗口
猜你喜欢
  • 2014-03-19
  • 2019-09-14
  • 2016-10-03
  • 2011-07-18
  • 1970-01-01
  • 2011-08-10
  • 2016-01-24
  • 1970-01-01
相关资源
最近更新 更多