界面线程与消息循环
发布日期:2021-05-14 14:58:43 浏览次数:17 分类:精选文章

本文共 3781 字,大约阅读时间需要 12 分钟。

界面线程与消息循环

消息循环

消息与消息循环是 GUI 开发中的核心概念。在 Windows Form 开发中,所有的界面消息都在一个持续运行的 while 循环内处理。这个循环不断地获取消息并逐一处理它们。

消息循环的工作原理可以通过以下伪代码简要体现:

while (message = GetMessage()) {
// 依次处理 message...
}

在实际编程中,消息循环通常由 Application.Run() 方法启动。例如:

Application.Run(new Form1());

具体来说,消息处理遵循以下步骤:

  • 调用窗口处理回调 WndProc,以便在消息循环中进一步分发消息。
  • 每个消息(如鼠标事件、键盘事件等)都有对应的控件处理逻辑。
  • 所有界面事件回调实际上都在消息循环中运行,因此需要确保它们能够及时返回,避免阻塞消息循环。这是确保界面不卡顿的关键。


    界面卡顿

    当一个按钮的点击事件处理程序占用较长时间(例如多秒)时,界面会显得卡顿或不响应。原因在于消息循环无法及时处理下一个消息。

    解决方法

    • 第1原则:确保所有消息处理回调都能快速返回。处理时间过长会导致界面卡顿,建议不超过 300 毫秒。
    • 第2原则:耗时较长的任务应放入工作线程中执行。

    工作线程

    为了处理耗时较长的任务,例如查询数据库、上传下载或编解码,可以使用工作线程。工作线程可以在后台运行任务,同时保证界面不卡顿。

    示例:创建工作线程

    public partial class Form1 : Form
    {
    public Form1()
    {
    InitializeComponent();
    }
    private void button1_Click(object sender, EventArgs e)
    {
    Thread th = new Thread(new ThreadStart(this.Execute));
    th.Start();
    }
    private void Execute()
    {
    Console.WriteLine("开始执行任务...");
    Thread.Sleep(5000); // 模拟长时间处理
    Console.WriteLine("任务完成");
    }
    }
    • 界面线程:处理界面事件和消息循环。
    • 工作线程:负责长时间的后台任务。

    原则总结:

  • 第1原则:避免长时间占用界面事件处理。
  • 第2原则:长任务不能放在主线程,使用工作线程代替。

  • 界面更新

    使用工作线程更新界面时,需遵循以下规则:

  • 第3原则:在工作线程中不能直接更新 UI 组件,必须通过 Invoke 方法上报消息到主线程。
  • 正确做法:使用 Invoke 推送自定义消息到消息循环中,让主线程执行相应的 UI 更新逻辑。
  • 错误的实现示例:

    using System;
    using System.Threading;
    public partial class Form1 : Form
    {
    private void button1_Click(object sender, EventArgs e)
    {
    Thread th = new Thread(new ThreadStart(this.Execute));
    th.Start();
    }
    private void Execute()
    {
    this.TextBox1.AppendText("3..\r\n");
    Thread.Sleep(1000);
    this.TextBox1.AppendText("2..\r\n");
    Thread.Sleep(1000);
    this.TextBox1.AppendText("1..\r\n");
    Thread.Sleep(1000);
    this.TextBox1.AppendText("OK..\r\n");
    }
    }
    • 错误原因:直接在工作线程中操作 UI 组件会导致 HWND 键盘损坏,会报错。
    • 正确实现:通过 Invoke 方法将更新操作推送到主线程。

    Action与Func

    ActionFunc 是常用的委托类型,用于定义接收单个参数或多个参数的方法。

    • Action<T>:适用于返回值为 void 的方法。
    • Func<T1, T2, ... , Tn>:适用于返回值不为 void 的方法。

    示例:

    public partial class Form1 : Form
    {
    public Form1()
    {
    InitializeComponent();
    }
    private void button1_Click(object sender, EventArgs e)
    {
    Thread th = new Thread(new ThreadStart(this.Execute));
    th.Start();
    }
    private void Execute()
    {
    this.Invoke(new Action
    (this.ShowProgress), "3..\r\n");
    Thread.Sleep(1000);
    this.Invoke(new Action
    (this.ShowProgress), "2..\r\n");
    Thread.Sleep(1000);
    this.Invoke(new Action
    (this.ShowProgress), "1..\r\n");
    Thread.Sleep(1000);
    this.Invoke(new Action
    (this.ShowProgress), "OK..\r\n");
    }
    public void ShowProgress(string text)
    {
    this.TextBox1.AppendText(text);
    }
    }

    InvokeRequired

    Control.InvokeRequired 用于检查当前线程是否是 GUI 线程。

    public partial class Form1 : Form
    {
    public Form1()
    {
    InitializeComponent();
    }
    private void button1_Click(object sender, EventArgs e)
    {
    Thread th = new Thread(new ThreadStart(this.Execute));
    th.Start();
    }
    private void Execute()
    {
    ShowProgress("3\r\n");
    Thread.Sleep(1000);
    ShowProgress("2\r\n");
    Thread.Sleep(1000);
    ShowProgress("1\r\n");
    Thread.Sleep(1000);
    ShowProgress("OK\r\n");
    }
    public void ShowProgress(string str)
    {
    if (this.InvokeRequired)
    {
    Console.WriteLine("Call In Work Thread : " + str);
    this.Invoke(new Action
    (this.ShowProgress), str);
    }
    else
    {
    Console.WriteLine("Call In Message Loop : " + str);
    this.TextBox1.AppendText(str);
    }
    }
    }

    总结

    通过以上优化,可以更好地理解和应用 Windows Form 的多线程开发原理。遵循各个原则可以有效避免界面卡顿,并确保 UI 更新的正确性。

    上一篇:(实例)ZIP解压缩
    下一篇:(实例)移动飞机

    发表评论

    最新留言

    做的很好,不错不错
    [***.243.131.199]2025年04月19日 16时47分50秒