从 ActiveX 调用时未释放 WPF 内存
WPF Memory not released when called from ActiveX
我有一个 class 将 1000 个组合框填充到 UI,代码是
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;
}
}
}
我为 class 创建了一个 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)没有得到释放,即使我删除了组合框和将网格内容设置为空。即使 GC.Collect() 也没有效果,因为它只会将垃圾添加到队列中。有没有解决办法。
我也试过在link中刷新内存,但即使这样内存仍然存在。(因为我不确定当前进程的句柄是否来自演示project/from ActiveX内存)
编辑
我添加了行
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();
}
上面的代码还是锁了内存,我不明白区别,这只是另一个事件,有人对此有任何想法吗?
目前我对 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
[=20 之后=]
编辑
最后,如果需要启动一个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.Open/CloseWindow() 方法是什么样子,但我想我得到了这个问题的重现。使用线程当然是问题的一部分,您必须做一件非直观的事情来防止泄漏具有线程关联的内部 WPF 管道对象。
必须关闭线程的调度程序。当 UI 线程终止时,通常 只会在 WPF 应用程序中发生一次。调度程序进行调度也非常重要,WPF 严重依赖它来确保 "weak events" 实际上很弱。
不泄漏的 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() 调用并且内存使用稳定。
我有一个 class 将 1000 个组合框填充到 UI,代码是
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;
}
}
}
我为 class 创建了一个 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)没有得到释放,即使我删除了组合框和将网格内容设置为空。即使 GC.Collect() 也没有效果,因为它只会将垃圾添加到队列中。有没有解决办法。
我也试过在link中刷新内存,但即使这样内存仍然存在。(因为我不确定当前进程的句柄是否来自演示project/from ActiveX内存)
编辑
我添加了行
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();
}
上面的代码还是锁了内存,我不明白区别,这只是另一个事件,有人对此有任何想法吗?
目前我对 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
[=20 之后=]
编辑
最后,如果需要启动一个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.Open/CloseWindow() 方法是什么样子,但我想我得到了这个问题的重现。使用线程当然是问题的一部分,您必须做一件非直观的事情来防止泄漏具有线程关联的内部 WPF 管道对象。
必须关闭线程的调度程序。当 UI 线程终止时,通常 只会在 WPF 应用程序中发生一次。调度程序进行调度也非常重要,WPF 严重依赖它来确保 "weak events" 实际上很弱。
不泄漏的 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() 调用并且内存使用稳定。