2015-10-13 322 views
0

令人驚訝的是,通過異步重複調用Window.ShowDialog可能導致堆棧溢出異常。C#WPF 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); 
     } 
    } 

P.S.你在這裏看到的代碼是專門爲了說明問題而構建的,所以不要關注爲什麼有人會這樣做。該問題的目的是瞭解ShowDialog爲什麼會以這種方式運行。

+0

看着堆棧應該澄清問題......如果它沒有提供足夠的信息給你 - 發佈堆棧的小部分重複的問題。 –

回答

4

由於您應該能夠看到堆棧跟蹤,因此每次調用ShowDialog()時都會將新幀推入堆棧。由於您在不關閉的情況下多次呼叫ShowDialog(),每次調用都會增加堆棧深度,堆棧最終會溢出。

發生這種情況的原因是,與Show()方法不同,ShowDialog()直到它顯示的窗口關閉纔會返回。這與任何其他方法調用一樣,所以它會導致堆棧增長。由於ShowDialog()必須響應用戶輸入,它會啓動一個新的Dispatcher循環。由於分派器仍在運行,您的計時器會持續觸發並打開新的嵌套對話框。

因此,在非常高的水平調用棧看起來像:

...Dispatcher Loop... 
TheCall 
ShowDialog 
... Dialog Dispatcher Loop... 
TheCall 
ShowDialog 
... Dialog Dispatcher Loop... 
TheCall 
ShowDialog 
... Dialog Dispatcher Loop... 

最終將溢出的新對話框打開。

+0

響應「發生這種情況是因爲與Show()方法不同,ShowDialog()不會返回,直到它顯示的窗口關閉爲止,這與任何其他方法調用一樣,所以它會導致堆棧增長。我不同意。如果我用其他阻塞的東西(不讓TheCall返回)替代對ShowDialog的調用,則堆棧不會堆疊(如預期的那樣)。所以這兩句話至少是誤導性的。但是新的Dispatcher循環很有意義,所以感謝你的答案。 +1 –