C#WinForms表单计时器未在循环内滴答

时间:2019-05-09 16:14:43

标签: c# .net visual-studio winforms timer

我创建了一个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));
        }


    }
}

1 个答案:

答案 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