[英]Pause execution of a method without locking GUI. C#
我正在使用C#開發一個卡片游戲,用於我的OOP介紹中的一個項目,現在讓游戲正常運行,但我正在為GUI添加“天賦”。
目前,卡片即時處理並顯示在用戶界面上。 我希望在交易卡之前暫停程序暫停一會兒。
當游戲啟動時,以下代碼運行以填充代表它們的PictureBox(最終將是一個循環):
cardImage1.Image = playDeck.deal().show();
cardImage2.Image = playDeck.deal().show();
cardImage3.Image = playDeck.deal().show();
cardImage4.Image = playDeck.deal().show();
cardImage5.Image = playDeck.deal().show();
...
我嘗試使用System.Threading.Thread.Sleep(100); 在每個deal()。show()之間以及每個方法中,但它實現的只是鎖定我的GUI,直到所有的睡眠處理完畢,然后一次顯示所有的卡片。
我也嘗試過使用計時器和while循環的組合但是它產生了相同的效果。
達到預期結果的最佳方法是什么?
問題是您在UI上運行的任何代碼都將阻止UI並凍結程序。 當您的代碼正在運行時(即使它正在運行Thread.Sleep
),發送到UI的消息(例如Paint或Click)將不會被處理(直到控制在您退出事件處理程序時返回到消息循環),導致它凍結。
執行此操作的最佳方法是在后台線程上運行,然后在睡眠之間Invoke
UI線程,如下所示:
//From the UI thread,
ThreadPool.QueueUserWorkItem(delegate {
//This code runs on a backround thread.
//It will not block the UI.
//However, you can't manipulate the UI from here.
//Instead, call Invoke.
Invoke(new Action(delegate { cardImage1.Image = playDeck.deal().show(); }));
Thread.Sleep(100);
Invoke(new Action(delegate { cardImage2.Image = playDeck.deal().show(); }));
Thread.Sleep(100);
Invoke(new Action(delegate { cardImage3.Image = playDeck.deal().show(); }));
Thread.Sleep(100);
//etc...
});
//The UI thread will continue while the delegate runs in the background.
或者,您可以制作計時器並在下一個計時器刻度中顯示每個圖像。 如果你使用計時器,你應該在開始時做的就是啟動計時器; 不要等待它,否則你會引入同樣的問題。
便宜的出路是循環調用Application.DoEvents(),但更好的選擇是設置一個System.Windows.Forms.Timer,它會在第一次經過后停止。 在任何一種情況下,您都需要一些指示器來告訴您的UI事件處理程序忽略輸入。 如果它足夠簡單,您甚至可以將timer.Enabled屬性用於此目的。
通常我只是推薦這樣的函數來執行暫停,同時允許UI是交互式的。
private void InteractivePause(TimeSpan length)
{
DateTime start = DateTime.Now;
TimeSpan restTime = new TimeSpan(200000); // 20 milliseconds
while(true)
{
System.Windows.Forms.Application.DoEvents();
TimeSpan remainingTime = start.Add(length).Subtract(DateTime.Now);
if (remainingTime > restTime)
{
System.Diagnostics.Debug.WriteLine(string.Format("1: {0}", remainingTime));
// Wait an insignificant amount of time so that the
// CPU usage doesn't hit the roof while we wait.
System.Threading.Thread.Sleep(restTime);
}
else
{
System.Diagnostics.Debug.WriteLine(string.Format("2: {0}", remainingTime));
if (remainingTime.Ticks > 0)
System.Threading.Thread.Sleep(remainingTime);
break;
}
}
}
但是,當從事件處理程序(例如按鈕單擊)中調用此解決方案時,似乎存在一些復雜性。 我認為系統希望按鈕單擊事件處理程序在繼續處理其他事件之前返回,因為如果我在事件處理程序仍在運行時再次嘗試單擊,則按鈕再次按下,即使我正在嘗試拖動表單而不是點擊按鈕。
所以這是我的選擇。 在表單中添加一個計時器,並創建一個經銷商類,通過與該計時器交互來處理卡片處理。 設置計時器的Interval屬性以匹配您希望處理卡的時間間隔。 這是我的示例代碼。
public partial class Form1 : Form
{
CardDealer dealer;
public Form1()
{
InitializeComponent();
dealer = new CardDealer(timer1);
}
private void button1_Click(object sender, EventArgs e)
{
dealer.QueueCard(img1, cardImage1);
dealer.QueueCard(img2, cardImage2);
dealer.QueueCard(img3, cardImage1);
}
}
class CardDealer
{
// A queue of pairs in which the first value represents
// the slot where the card will go, and the second is
// a reference to the image that will appear there.
Queue<KeyValuePair<Label, Image>> cardsToDeal;
System.Windows.Forms.Timer dealTimer;
public CardDealer(System.Windows.Forms.Timer dealTimer)
{
cardsToDeal = new Queue<KeyValuePair<Label, Image>>();
dealTimer.Tick += new EventHandler(dealTimer_Tick);
this.dealTimer = dealTimer;
}
void dealTimer_Tick(object sender, EventArgs e)
{
KeyValuePair<Label, Image> cardInfo = cardInfo = cardsToDeal.Dequeue();
cardInfo.Key.Image = cardInfo.Value;
if (cardsToDeal.Count <= 0)
dealTimer.Enabled = false;
}
public void QueueCard(Label slot, Image card)
{
cardsToDeal.Enqueue(new KeyValuePair<Label, Image>(slot, card));
dealTimer.Enabled = true;
}
}
我會嘗試在另一個線程中放置處理牌組的代碼(並調用Thread.Sleep)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.