令人惊讶的是,反复调用Window会导致堆栈溢出异常。异步ShowDialog。
public MainWindow()
{
InitializeComponent();
TheCallDelegate = TheCall;
_timer = new DispatcherTimer();
_timer.Tick += _timer_Tick;
_timer.Start();
}
DispatcherTimer _timer = null;
void _timer_Tick(object sender, EventArgs e)
{
_timer.Dispatcher.BeginInvoke(TheCallDelegate);
}
Action TheCallDelegate;
void TheCall()
{
Window win = new Window();
win.ShowDialog();
}
正如你所看到的,这里没有实际的递归(或者不应该有),但是一旦异常发生,你可以看到调用堆栈确实是满的。为什么?这也可以在不使用定时器的情况下实现,如下所示:
private async void Button_Click(object sender, RoutedEventArgs e)
{
while (true)
{
this.Dispatcher.BeginInvoke(TheCallDelegate);
await Task.Delay(1);
}
}
注:您在这里看到的代码是专门用来说明这个问题的,所以不要关注为什么有人会这样做。这个问题的目的是理解为什么ShowDialog以这种方式运行。
您应该能够看到堆栈跟踪,每次调用ShowDialog()
都会将新帧推送到堆栈上。由于您多次调用ShowDialog()
而不关闭,每次调用都会增加堆栈深度,并且堆栈最终会溢出。
发生这种情况是因为与Show()
方法不同,ShowDialog()
直到它显示的窗口关闭才返回。这与任何其他方法调用一样,因此它会导致堆栈增长。由于ShowDialog()
必须响应用户输入,因此它启动一个新的Dispatcher循环。由于Dispatcher仍在运行,因此计时器会不断触发并打开新的嵌套对话框。
那么在非常高的级别上,你的调用堆栈看起来像:
...Dispatcher Loop...
TheCall
ShowDialog
... Dialog Dispatcher Loop...
TheCall
ShowDialog
... Dialog Dispatcher Loop...
TheCall
ShowDialog
... Dialog Dispatcher Loop...