0

不要使用 DoEvents()。使用线程!

这个口头禅正在互联网上漫游,包括 SO。好的,所以我创建了一个简短的概念证明,我尝试只使用线程。所以基本上按钮应该做的是触发向下移动蓝色框。

它在单独的线程中运行 YET windows 窗体没有响应(我无法移动它或再次单击按钮),直到它完成向下移动。

问题是,我搞砸了什么?如果我不应该使用 DoEvents(),那该怎么办?(如果您取消注释该Application.DoEvents()行,它将变得响应)。

代码

using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

namespace doevents
{
    public partial class Main : Form
    {
        // Use static so that I don't have to pass them over and over
        public static TableLayoutPanel movingBox;
        public static Form mainForm;

        // Initialize
        public Main()
        {
            InitializeComponent();

            Main.mainForm = this;
            Main.movingBox = tableLayoutPanel1;
        }

        // Button that runs thread which will move the blue box down
        private void button1_Click(object sender, EventArgs e)
        {
            new Thread(
                new ThreadStart(
                    () => 
                        {
                            SlideBoxDown();
                        }
                    )
                ).Start();
        }

        // Delegate
        delegate void SlideBoxDownCallback();

        // Slide box down
        private static void SlideBoxDown()
        {
            if (Main.movingBox.InvokeRequired)
            {
                SlideBoxDownCallback d = new SlideBoxDownCallback(SlideBoxDown);
                Main.mainForm.Invoke(d, new object[] { });
            }
            else
            {
                for (int i = 0; i < 20; i++)
                {
                    Main.movingBox.Location = new Point(Main.movingBox.Location.X, Main.movingBox.Location.Y + 2);
                    Thread.Sleep(100);
                    //Application.DoEvents();
                }
            }
        }       
    }
}

应用布局

在此处输入图像描述

4

3 回答 3

3

您仍在调用Thread.Sleep(100)主线程。因为SlideBoxDown 正在调用自身回到 GUI 线程。由于这是线程中唯一发生的事情,因此您的程序本质上是单线程的。

for (int i = 0; i < 20; i++)根本不是控制动画的好方法。
使用计时器。

关于(如果您取消注释 Application.DoEvents() 行,它将变得响应) - 正确,但是然后尝试在该动画中间关闭表单。你不会喜欢的。

于 2014-08-14T16:47:22.250 回答
1

您创建一个新线程,然后在您从新线程调用的方法中编组回 UI 线程,然后在 UI 线程中运行所有其余代码。这很好地违背了首先启动新线程的目的。

要每隔 X 时间间隔执行一次操作,在这种情况下要每 100 毫秒移动一次对象,请使用Timer.

于 2014-08-14T16:49:14.957 回答
-1

对应用程序 UI 的更改需要 UI 线程。这就是它的全部。您的代码正在创建一个后台线程,其唯一目的是将消息传递到应用程序的消息泵,该消息泵由 UI 线程使用。因此,“else”子句中发生的所有事情,包括 Thread.Sleep() 调用,都在主线程上执行。

相反,我会重构此代码以用计时器替换睡眠。Timer 将在后台线程上运行,并且可以在其“Tick”事件(间隔 100 毫秒)上触发处理程序,该处理程序可以将矩形移动一个增量。这样,绘制基于 UI 线程上的消费消息进行,但等待由后台线程完成,从而保持 UI 响应。

于 2014-08-14T16:48:51.270 回答