使用 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
,因此您需要在包含Bitmap
到 Tick
使其显示;让我们看看如何做到这一点:
如果您正在修改外部 Bitmap bmp
,您将需要重新分配它:
bmp.SetPixel(...);
panel1.BackgroundImage = bmp;
如果您直接修改 Image
或 BackgroundImage
,Invalidate
的 Refresh
将执行:
((Bitmap)panel1.BackgroundImage).SetPixel(...);
panel1.Invalidate();
我在 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
,因此您需要在包含Bitmap
到 Tick
使其显示;让我们看看如何做到这一点:
如果您正在修改外部 Bitmap bmp
,您将需要重新分配它:
bmp.SetPixel(...);
panel1.BackgroundImage = bmp;
如果您直接修改 Image
或 BackgroundImage
,Invalidate
的 Refresh
将执行:
((Bitmap)panel1.BackgroundImage).SetPixel(...);
panel1.Invalidate();