使用 await 减慢 Winforms 中的循环

Slowing down loop in Winforms with await

我在 Windows 表单应用程序中有一个循环,它以特定形状绘制像素,但我想让用户看到它正在绘制。

有问题的循环:

public async void Bar()
    {
        for (int i = 0; i < 10000; i++)
        {
            await Task.Delay(100);

            Foo();
        }
    }

Bar() 只是从 UI 线程上的非静态函数调用。

Foo() 应该在位图上绘制一个像素,并执行(根据调试器)但实际上并没有绘制任何东西。很不寻常。

如果我删除 await 语句,它会完美绘制。

您可以使用您的代码,但您需要强制 UI-线程更新您修改的位图。为此,请将此行添加到您的 Foo:

   panel1.Refresh();

这假定您直接属性 写入显示的位图(见下文!)。当然你需要将 panel1 更改为持有 Bitmap 的控件;请参阅下文了解使用 external Bitmap 变量或 'internal' Image 的两种基本情况(对于 PictureBox) 或 BackgroundImage(对于 Panel 或许多其他)属性 的控件!

如果您想在位图上工作 变量 您的 Foo 可能如下所示:

public void Foo()
{
    Bitmap bmp = (Bitmap) panel1.BackgroundImage;
    // now do your drawing stuff
    bmp.SetPixel(...);
    panel1.BackgroundImage = bmp;
    panel1.Refresh();
}

虽然这可行,但动画通常是使用 Timer.

使用 Timer 的关键是打破循环:

将设置向上移动到 Button.Click 并将其余移动到 Timer.Tick,可能像这样:

计数器现在处于 class 水平:

    int counter = 0;

Tick持计数、支票、绘图体:

    private void timer1_Tick(object sender, EventArgs e)
    {
        if (counter > 1000)
        {
            timer1.Stop();
        }
        else
        {
            angle1 += angle1Modifier;
            angle2 += angle2Modifier;
            angle3 += angle3Modifier;
            DrawPixel(getPoint(angle1, angle2, angle3));
            yourCanvasControl.Refresh();
        }
    }

Click 保持设置并启动 loop/timer:

    private void buttonStart_Click(object sender, EventArgs e)
    {
        counter = 0;
        timer1.Interval = 50;
        timer1.Start();
    }   

之所以可行,是因为 Timer 与 'UI-thread' 相关联,后者是执行所有显示工作以及接受所有用户输入的一个线程。

显然你不应该阻止它,否则你的程序会变得迟缓或似乎冻结,但是 Timer,而不是 Thread.Sleep 不会阻止 UI-thread,所以它是动画的一个不错的选择(就它们在 Winforms 中的表现而言)。

注意:由于您使用的是 SetPixel,因此您正在修改 Bitmap,因此您需要在包含BitmapTick 使其显示;让我们看看如何做到这一点:

如果您正在修改外部 Bitmap bmp,您将需要重新分配它:

bmp.SetPixel(...);
panel1.BackgroundImage = bmp;

如果您直接修改 ImageBackgroundImageInvalidateRefresh 将执行:

((Bitmap)panel1.BackgroundImage).SetPixel(...);
panel1.Invalidate();