我创建了一个C#项目,一个带有背景类的WinForms应用程序。 WinForms App类包含一个点,并在每次调用“ Refresh()”方法时绘制该点。按下空格键,即可运行背景类。后台类提供了一个“ run()”方法,该方法将计算方法调用特定次数(while循环)。然后,该计算方法将执行一些计算,并将结果添加到堆栈中,然后启动计时器。此计时器包含一个draw方法,该方法从堆栈中获取数据,并告诉Form为每个堆栈条目打印一个圆圈。 (下面描述代码非常令人困惑,应该易于理解)
现在我遇到一个我不明白的非常奇怪的问题:(请参阅下面的示例代码) 在后台类中,当while循环调用计算时,将完成计算,但时间不会执行draw方法。但是,当您注释掉while循环的开始和结束并改为手动多次按空格键时,一切正常。为什么代码在没有循环的情况下工作,并且多次手动按下,而在while循环中却无法工作?以及如何解决它才能使其与循环配合使用?
重现此问题的步骤: 在Visual Studio中创建一个新的WinForms App,用下面的代码替换Form1.cs并用下面的代码创建BackgroundRunner.cs类。 然后只需运行项目并按空格键即可运行背景类。 要使代码正常工作: 只需注释掉while循环的开始和结束,然后手动多次按空格键即可。 (圆圈大喊大叫)
其他: 是的,在这个分解的示例中,创建两个单独的类和一个表单计时器看起来很奇怪,但是在整个项目中,我需要使它以这种方式工作。
谢谢。
我已经在Google上搜索,发现计时器和UI之类的解决方案需要在同一线程等上。但是我认为两者都在同一线程上,因为它没有循环。
这是Form1.cs类:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace TestApp
{
public partial class Form1 : Form
{
private Point p;
private BackgroundRunner runner;
private int count = 0;
private int max = 10;
public Form1() {
InitializeComponent();
p = new Point(0, 0);
runner = new BackgroundRunner(this);
}
private void Form1_Load(object sender, EventArgs e) {
}
//Method to refresh the form (draws the single point)
override
protected void OnPaint(PaintEventArgs e) {
drawPoint(p, e.Graphics);
}
//Method to draw a point
private void drawPoint(Point p, Graphics g) {
Brush b = new SolidBrush(Color.Black);
g.FillEllipse(b, p.X, p.Y, 10, 10);
}
//when you press the space bar the runner will start
override
protected void OnKeyDown(KeyEventArgs e) {
if(e.KeyValue == 32) {
runner.run();
}
}
public int getCount() {
return count;
}
public int getMax() {
return max;
}
//sets point, refreshes the form and increases the count
public void setPoint(Point pnew) {
p = pnew;
Refresh();
count++;
}
}
}
这是BackgroundRunner.cs类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace TestApp
{
class BackgroundRunner
{
private Form1 form;
private System.Windows.Forms.Timer timer;
private Stack<int> test;
private int max;
private int count;
public BackgroundRunner(Form1 formtemp) {
form = formtemp;
timer = new System.Windows.Forms.Timer();
timer.Interval = 100;
timer.Tick += drawMovement;
}
public void run() {
count = form.getCount();
max = form.getMax();
//Here happens the strange stuff:
//If you comment out the while loop start and end
//and press manually the space bar the circle will move
//from right to left an up to down.
//But if you use the while loop nothing will happen
//and the "drawMovement" Method will never be executed,
//because the message inside will never be written...
Console.WriteLine("Count: " + count + " Max: " + max);
while (count < max) {
Console.WriteLine("Calc Move");
calculateMovement();
count = form.getCount();
max = form.getMax();
Thread.Sleep(50);
}
}
//Just a method that adds data to the stack and then start the timer
private void calculateMovement() {
test = new Stack<int>(5);
test.Push(10);
test.Push(20);
test.Push(30);
test.Push(40);
test.Push(50);
timer.Start();
}
//Method that goes through the stack and updates
//the forms point incl refresh
private void drawMovement(object o, EventArgs e) {
Console.WriteLine("Draw Move");
if (test.Count <= 0) {
timer.Stop();
return;
}
int x = test.Pop();
int y = count;
form.setPoint(new System.Drawing.Point(x, y));
}
}
}
答案 0 :(得分:0)
从不阻塞发生消息循环的主线程。
计时器在消息转移中触发,这就是为什么在通过循环和睡眠阻塞主线程时未触发计时器的原因。
通过将async
更改为public void run()
来使方法public async void run()
将Threed.Sleep
替换为await Task.Delay
,然后循环将异步进行。
如果您的应用程序针对.NET Framewok 2.0或更高版本(肯定),则可能会引发异常,因为它不是线程安全的。
要解决此问题,请将所有访问UI控件的代码(可能只是抛出异常的位置,以防万一您不确定它们是什么)。
form.Invoke(new Action(()=>{
//Statements to access the UI controls
}));
更正
由于
SynchronizationContext
被发布到了,等待将在UI线程上返回执行。因此,关于跨线程不会有例外。
J.vanLangen