界面线程与消息循环

界 面 线 程 与 消 息 循 环 界面线程与消息循环 线

1.消息循环


消息与消息循环,是所有的GUI开发里共同的概念

消息 Message ,有的地方也叫事件

鼠标消息
键盘消息
绘制事件
窗口最大化、最小化......

消息循环,Message Loop
所有的界面消息,都是在一个while循环里处理的

用伪代码表示:

List<Message> msgList = new List<Message>()
while( message = GetMessage(O)
{
	依次处理message...
}

真实的消息循环:

Application.Run(new Form1());

具体的消息处理过程:

protected override void WndProc(ref Message m)
{
	base.WndProc(ref m);
}

所有的界面事件回调,本质上都运行在消息循环里在消息循环里,作进一步的分发处理

比如,一个Message是鼠标事件,则分发给相应的控件处理。

void button1_MouseUp(object sender, MouseEventArgs e){
}

界面线程
运行这个消息循环的线程,就是界面线程在WinForm里,主线程即界面线程

static void Main()
{
	Application.Run(new Form1());
}

所有的界面消息,是在一个while循环里处理的

2.界面卡顿

演示:点击按钮,运行一段处理程序。

按钮处理程序需要多秒完成
在这个时间段内整个界面是卡住的、不可操作的,为什么?


消息循环:每一个消息处理都要尽快完成

while ( message =GetMessage())
{
	switch (消息类型)
	case鼠标消息:...
	case键盘消忌:...
	....
}

第1原则:所有的消息处理回调,都要尽快返回
当处理时间太长时,界面会有卡顿之感(大于300毫秒左右)


3.工作线程


如果确实需要处理一件耗时较长的工作
例如,查询数据库,上传下载,编解码…都可能需要较长时间才能完成。怎么解决?


工作线程(Work Thread)

如果事件处理需要较长时间,应当创建一个线程来处理这个任务。此线程称为“工作线程”


实例:由于button1_Click()会立即返回,不会引起界面卡顿

  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()
        {
            // 此回调处理需要15秒才能完成
            Console.WriteLine("Ma...");
            Thread.Sleep(5000);

            Console.WriteLine("Mi...");
            Thread.Sleep(5000);

            Console.WriteLine("Hong...");
            Thread.Sleep(5000);

            Console.WriteLine("完成");
        }
    }
界面线程:一直运行,处理界面事件
工作线程:工作完成后退出

在这里插入图片描述

线程的特点:独立和并行
1原则:界面回调的处理不能太久,否则卡顿
第2原则:当任务时间较长时,则创建工作线程

4.界面的更新


使用工作线程,实现一个倒计划的效果
要求在文本框中显示倒计时:

3..
2..
1..
OK

错误的实现:

  • 1 创建工作线程
  • 2 在工作线程中直接更新TextBox的显示

观察:运行程序,程序会有崩溃提示

为什么不能在工作线程中直接访问textBox1呢?

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace 界面更新
{
    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()
        {
            // 此回调处理需要3秒才能完成
            //textBox1.AppendText("3..\r\n");
            this.Invoke(new MyCallback(this.ShowProgress), "3..\r\n");
            Thread.Sleep(1000);

            //textBox1.AppendText("2..\r\n");
            this.Invoke(new MyCallback(this.ShowProgress), "2..\r\n");
            Thread.Sleep(1000);

            this.Invoke(new MyCallback(this.ShowProgress), "1..\r\n");
            Thread.Sleep(1000);

            this.Invoke(new MyCallback(this.ShowProgress), "OK..\r\n");
        }

        public delegate void MyCallback(string str);

        public void ShowProgress(string text)
        {
            // 这个方法是在消息循环(界面线程)里执行的
            textBox1.AppendText(text);
        }
    }
}

在工作线程中访问UI控件时,需使用Invoke方法Control.Invoke(method, args)

当调用Invoke时,实际上推送了一个自定义的消息到消息循环中。
当消息被处理时,相应的回调被执行。


正确的实现:

1 定义一个委托类型
2 定义一个回调处理
3 使用Invoke推送一个自定义事件到消息循环

注意:Invoke消息的回调也是在界面线程中执行的


第1原则:界面回调的处理不能太久,否则卡顿
第2原则:当住务时间较长时,则创建工作线程
第3原则:在工作线程中不可以直接更新UI,需借助Invoke来发送一个自定义的消息


5.Action与Func

委托,实际上是对一类方法的特征描述
例如

public delegate void MyCallback(string str);

表示的是“参数为string、返回值为void”的方法


两个通用的 Delegate:

System.Action,表示返回值为void的方法
System.Func表示返回值不是void的方法

几乎所有的方法,都可以用这两种委托来表示


例如:

void test1 (string a, int b)

由于返回值是void类型,可以用Action表示

new Action<string,int> (this.test1)

例如:

Student test2 (string a, int b)

由于返回值不是void,可以用Func表示

new Func<string, int , Stiident> (this.test2)

在工作线程里更新UI时,直接使用Action / Func即可,
不需要专门定义一个Delegate

this.Invoke( new Action<string>(this.ShowProgress) )

public void ShowProgress(string text)
{
}
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()
        {
            // 此回调处理需要3秒才能完成
            //textBox1.AppendText("3..\r\n");
            this.Invoke(new Action<string>(this.ShowProgress), "3..\r\n");
            Thread.Sleep(1000);

            //textBox1.AppendText("2..\r\n");
            this.Invoke(new Action<string>(this.ShowProgress), "2..\r\n");
            Thread.Sleep(1000);

            this.Invoke(new Action<string>(this.ShowProgress), "1..\r\n");
            Thread.Sleep(1000);

            this.Invoke(new Action<string>(this.ShowProgress), "OK..\r\n");
        }

        //public delegate void MyCallback(string str);

        public void ShowProgress(string text)
        {
            // 这个方法是在消息循环(界面线程)里执行的
            textBox1.AppendText(text);
        }

    }

6.InvokeRequired

Control.InvokeRequired用来判断是不是在工作线程

if (this.InvokeRequired )
{
	//	判断当前线程是不是工作线程
}

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<string>(this.ShowProgress),str);
            }
            else
            {
                // 从界面线程中调用
                Console.WriteLine("Call In Message Loop :" + str);
                textBox1.AppendText(str);
            } 
        }


    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值