2014-02-11 71 views
2

我有一個遊戲網格更新「步驟」(康威的生活遊戲,雖然這並不涉及我的問題,特別是)。我試圖創建一個自動運行模擬的播放按鈕,直到再次按下該按鈕暫停模擬。起初,我有這樣的事情:創建無限循環而不打破程序

public partial class MainWindow : Window 
{ 
    bool running = true; 
    public MainWindow() 
    { 
     InitializeComponent(); 
     bool isProgramRunning = true; 
     while(isProgramRunning) 
     { 
      while(running) 
      ProcessGeneration(); 
     } 
    } 
} 

我的按鈕,播放/暫停仿真具有以下click處理程序:

private void PlayClick_Handler(object sender, RoutedEventArgs e) 
{   
    PlayButton.Content = ((string)PlayButton.Content == "Play") ? "Pause" : "Play"; 

    running = (running) ? false : true; 
} 

我認爲,這將讓我的計劃只是不斷循環(isProgramRunning永不結束)反覆檢查「running」是true還是false,並且按下按鈕切換「running」將允許我循環/跳出循環。但是隻有while(isProgramRunning)部分殺死了程序(它甚至不會加載)。實際上,每次我嘗試使用while()時,程序都會停止響應。有沒有辦法解決這個問題?

+1

一句忠告:你可以學到很多* *關於通過實施高斯帕的算法生命功能的編程;它允許您支持荒謬的大型主板和荒謬的快速計算 - 每秒鐘有數萬億個單元和數萬億次的主板 - 所有這些都使用完美的普通硬件。 –

+0

您可以在UI線程上運行[後臺異步操作](http://stackoverflow.com/a/21713337/1768303),以避免阻塞WPF分派器循環。 – Noseratio

回答

5

可能你不希望你的ProcessGeneration()發生儘可能快,屏幕上的所有內容都會變得模糊不清。你也不想阻塞UI線程。可以用一塊石頭殺死兩隻鳥,一個是Timer

創建一個計時器並讓它每隔1/4秒運行一次或者經常更新。然後,在您的啓動和停止代碼中,您只需啓用或禁用定時器即可。

public partial class MainWindow : Window 
{ 
    private readonly System.Timers.Timer _timer; 

    public MainWindow() 
    { 
     InitializeComponent(); 

     _timer = new Timer(250); //Updates every quarter second. 
     _timer.Elapsed += new ElapsedEventHandler(OnTimedEvent); 
    } 

    private void OnTimedEvent(object source, ElapsedEventArgs e) 
    { 
     ProcessGeneration(); 
    } 

    private void PlayClick_Handler(object sender, RoutedEventArgs e) 
    {   
     var enabled = _timer.Enabled; 
     if(enabled) 
     { 
      PlayButton.Content = "Play"; 
      _timer.Enabled = false; 
     } 
     else 
     { 
      PlayButton.Content = "Pause"; 
      _timer.Enabled = true; 
     } 
    } 

} 
0

爲了不停止運行該程序的主線程,添加一個單獨的線程來ProcessGeneration方法。點擊按鈕後,停止該線程。此外,它確實取決於您正在運行的進程。如果它們不連續,請按照@ScottChamberlain的說法使用計時器。如果你需要某個動作一直執行,那麼使用一個單獨的線程,這樣它就不會阻塞你的主線程。

7

Scott Chamberlain的建議是正確的。我會補充一點,它有助於理解您的程序停止響應的原因。這是因爲你看不到另一個無限循環。它看起來像這樣:

while(true) 
{ 
    get the next notification from the operating system 
    if its a quit message, exit 
    otherwise, run the event handler associated with the message 
} 

這個循環得到一個消息,說「節目開始」,所以它運行你的構造函數,然後在循環坐在那裏永遠。它永遠不會返回到消息循環,因此鼠標點擊消息永遠不會從隊列中取出,從不處理,因此您的循環不會停止。

計時器是一個好主意。還有其他技巧可以使用。我不建議創建一個工作者線程;多線程爲您的程序帶來了巨大的複雜性。我也建議不要使用DoEvents;它會遞歸地啓動一個第二個消息循環,這會引入重入錯誤。我建議在C#5中使用await,儘管將頭戴在頭上可能有點棘手。

+0

埃裏克,會像[this](http://stackoverflow.com/a/21713337/1768303)接近你推薦'await'? – Noseratio

+1

@Noseratio:就是這樣的,是的。 –

0

你已經證明是阻斷WPF調度事件循環的代碼:

public partial class MainWindow : Window 
{ 
    bool running = true; 
    public MainWindow() 
    { 
     InitializeComponent(); 
     bool isProgramRunning = true; 
     while(isProgramRunning) 
     { 
      while(running) 
      ProcessGeneration(); 
     } 
    } 
} 

相反,你可以異步運行你的核心ProcessGeneration循環,由Eric Lippert的答案的建議。通過這種方式,分派事件不斷得到處理(注await Dispatcher.Yield):

async Task ProcessGenerationsAsync(CancellationToken token) 
{ 
    while (this.isProgramRunning) 
    { 
     // observe cancellation (optional) 
     token.ThrowIfCancellationRequested(); 

     // yield to the Dispatcher event loop to process user input 
     await Dispatcher.Yield(DispatcherPriority.Background); 

     // optional throttling 
     await Task.Delay(50, token); 

     // do the next update 
     ProcessGeneration(); 
    } 
} 

// ... 

public partial class MainWindow : Window 
{ 
    bool running = true; 
    public MainWindow() 
    { 
     InitializeComponent(); 

     this.isProgramRunning = true; 

     this.cts = new CancellationTokenSource(); 
     this.processGenerationTask = ProcessGenerationsAsync(cts.Token); 
    } 
}