暂停方法的执行而不锁定GUI。C#

暂停方法的执行而不锁定GUI。C#,c#,methods,sleep,wait,halt,C#,Methods,Sleep,Wait,Halt,我正在为我的OOP简介论文上的一个项目用C#编写一个纸牌游戏,现在游戏已经开始运行了,但我正在向GUI添加“天赋” 目前,卡片被处理并立即出现在用户界面上。我想在发完一张牌后,在发下一张牌之前,我必须对暂停进行编程 当游戏启动时,运行以下代码以填充表示它们的PictureBox(最终将是一个循环): 我尝试过使用System.Threading.Thread.Sleep(100);在每个deal().show()之间,以及在每个方法内部,但它所实现的只是锁定我的GUI,直到所有睡眠都已处理完毕,

我正在为我的OOP简介论文上的一个项目用C#编写一个纸牌游戏,现在游戏已经开始运行了,但我正在向GUI添加“天赋”

目前,卡片被处理并立即出现在用户界面上。我想在发完一张牌后,在发下一张牌之前,我必须对暂停进行编程

当游戏启动时,运行以下代码以填充表示它们的PictureBox(最终将是一个循环):

我尝试过使用System.Threading.Thread.Sleep(100);在每个deal().show()之间,以及在每个方法内部,但它所实现的只是锁定我的GUI,直到所有睡眠都已处理完毕,然后立即显示所有卡

我也尝试过使用定时器和while循环的组合,但它产生了相同的效果

实现所需结果的最佳方法是什么?

问题是,在UI上运行的任何代码都会阻塞UI并冻结程序。当您的代码正在运行时(即使它正在运行
Thread.Sleep
),发送到UI的消息(如绘制或单击)将不会被处理(直到您退出事件处理程序时控件返回到消息循环),导致其冻结

最好的方法是在后台线程上运行,然后在睡眠之间运行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.


或者,您可以制作一个计时器,并在下一个计时器滴答声中显示每个图像。如果你使用定时器,你在开始时所要做的就是启动定时器;不要等待它,否则您将引入相同的问题。

我将尝试将处理数据组(并调用Thread.Sleep)的代码放在另一个线程中。

廉价的解决方法是循环调用Application.DoEvents()但更好的选择是设置System.Windows.Forms.Timer,在第一次超时后停止。在这两种情况下,您都需要一些指示符来告诉UI事件处理程序忽略输入。如果足够简单的话,您甚至可以使用timer.Enabled属性来实现此目的。

通常我只建议使用这样的函数来执行暂停,同时允许用户界面交互

  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;
  }
}
公共部分类表单1:表单
{
信用卡经销商;
公共表格1()
{
初始化组件();
经销商=新卡经销商(计时器1);
}
私有无效按钮1\u单击(对象发送者,事件参数e)
{
经销商排队卡(img1,cardImage1);
经销商排队卡(img2、cardImage2);
经销商排队卡(img3、cardImage1);
}
}
等级信用卡经销商
{
//第一个值表示的成对队列
//卡要放的插槽,第二个是
//对将出现在此处的图像的引用。
排队交易;
System.Windows.Forms.Timer;
公共卡经销商(System.Windows.Forms.Timer)
{
cardsToDeal=新队列();
dealTimer.Tick+=新事件处理程序(dealTimer\u Tick);
this.dealTimer=dealTimer;
}
void dealTimer_Tick(对象发送方,事件参数e)
{
KeyValuePair cardInfo=cardInfo=cardsToDeal.Dequeue();
cardInfo.Key.Image=cardInfo.Value;

if(cardsToDeal.Count谢谢,该委托工作得很好!而且我从未想过要将实际的交易调用放入计时器的刻度中(我只是在其中执行计数++类型的场景)这可能在将来的某个时候也会很有用!正如我所解释的,在计时器中执行
count++
不会有任何帮助,因为while循环中的代码仍然会阻塞UI线程。顺便说一句,也有可能滥用迭代器来执行此操作;如果您感兴趣,我会解释。请注意,这会将所有内容路由回mai如果在deal()中涉及到一些工作,那么分解语句并只调用show()是值得的。
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;
  }
}