在C#中以时间间隔运行for循环

时间:2015-01-19 05:14:52

标签: c# multithreading winforms timer

我正在尝试制作一个简单的C#程序,它可以滚动两个骰子100次。对于每个卷筒,骰子图像和一些标签应在MainWindow中更改。为了看到每个卷,我需要某种时间控制。当我的循环运行(由单击事件触发)时,窗口会冻结一段时间,然后显示第100次滚动。所以看起来循环在后台运行(没有抛出异常),但是图像没有在窗口中更新。

使用Thread.Sleep(500)是我最接近这项工作的。我见过一些计时器解决方案,但没有一个允许for循环。每当我尝试使用计时器时,我都会遇到线程问题。

循环:

for (int i = 0; i < 100; i++)
{
    rollNumber++;
    labelRollNumber.Content = rollNumber;

    //creates two random integers to use for Image parameters
    int rdmInt1 = rdm.Next(0, 6);
    int rdmInt2 = rdm.Next(0, 6);

    //Changes dice images
    Image(rdmInt1, rdmInt2);

    Thread.Sleep(500);
}

1 个答案:

答案 0 :(得分:0)

  

所以看起来循环在后台运行

相反,它直接在UI线程上运行并阻止您的注意事项显示图像。让它进入睡眠也无济于事,因为那时你的应用程序什么都不做,一切都在睡觉。这只是一个巧合,500毫秒它的工作“更好”。

这个问题有很多解决方案,我会告诉你其中一个。

首先,我将此逻辑封装到一个类中,但您不必这样做。

class Dice
{
    // event fired when dice is rolled
    public event EventHandler<DiceEventArgs> OnRolled;

    public void Roll(int xTimes)
    {
        int rollNumber = 0;
        var rnd = new Random();

        for (int i = 0; i < xTimes; i++)
        {
            rollNumber++;
            int rdmInt1 = rnd.Next(0, 6);
            int rdmInt2 = rnd.Next(0, 6);

            if (OnRolled != null)
                OnRolled(null, new DiceEventArgs(rollNumber, rdmInt1, rdmInt2));

            Thread.Sleep(1000);
        }
    }
}

现在这些特殊事件args提供有关当前骰子滚动的所有信息

class DiceEventArgs : EventArgs
{
    private int rollNumber;
    private int dice1;
    private int dice2;

    public int RollNumber { get { return rollNumber; } }
    public int Dice1 { get { return dice1; } }
    public int Dice2 { get { return dice2; } }

    public DiceEventArgs(int rollNumber, int dice1, int dice2)
    {
        this.rollNumber = rollNumber;
        this.dice1 = dice1;
        this.dice2 = dice2;
    }
}

现在是UI部分

private async void button1_Click(object sender, EventArgs e)
{
    var dice = new Dice();
    dice.OnRolled += Dice_OnRolled;
    await Task.Run(() => dice.Roll(20));
}

void Dice_OnRolled(object sender, DiceEventArgs args)
{
    this.Invoke(new Action(() => { button1.Text = args.RollNumber.ToString(); }));
}
通过这种方式,GUI将始终保持响应。我通过替换开始滚动的按钮的Text属性来测试它,但您可以很好地调用图像更改。注意,你必须使用Invoke来调用它,因为可以从不同的线程触发事件。