当前位置: 首页 > news >正文

聊透多线程编程-线程基础-3.C# Thread 如何从非UI线程直接更新UI元素

目录

1. 使用 Control.Invoke 或 Control.BeginInvoke(Windows Forms)

2. 使用 Dispatcher.Invoke 或 Dispatcher.BeginInvoke(WPF)

3. 使用 SynchronizationContext


 

桌面应用程序(如 Windows Forms 或 WPF)中,UI 操作必须由主线程(也称 UI 线程)执行。如果尝试从非 UI 线程直接更新 UI 元素,通常会引发异常或导致不可预测的行为。

Thread 类本身无法直接更新 UI,但可以通过以下方法将操作委托给 UI 线程来实现安全的 UI 更新。


1. 使用 Control.Invoke 或 Control.BeginInvoke(Windows Forms)

在 Windows Forms 应用程序中,可以使用 Control.Invoke 或 Control.BeginInvoke 方法将代码调度到 UI 线程。

示例代码:

using System;
using System.Reflection.Emit;
using System.Threading;
using System.Windows.Forms;
using static System.Net.Mime.MediaTypeNames;

class Program : Form
{
    private Button button;
    private Label label;

    public Program()
    {
        button = new Button { Text = "Start Thread", Dock = DockStyle.Top };
        label = new Label { Text = "Waiting...", Dock = DockStyle.Fill };

        button.Click += Button_Click;

        Controls.Add(label);
        Controls.Add(button);
    }

    private void Button_Click(object sender, EventArgs e)
    {
        Thread thread = new Thread(UpdateLabel);
        thread.Start();
    }

    private void UpdateLabel()
    {
        for (int i = 0; i < 10; i++)
        {
            // 检查是否需要调用 Invoke
            if (label.InvokeRequired)
            {
                // 使用 Invoke 将操作调度到 UI 线程
                label.Invoke(new Action(() => label.Text = $"Count: {i}"));
            }
            else
            {
                // 如果当前线程是 UI 线程,则直接更新
                label.Text = $"Count: {i}";
            }

            Thread.Sleep(500); // 模拟工作
        }
    }

    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.Run(new Program());
    }
}

解释:

  • InvokeRequired:检查当前线程是否是创建控件的线程(即 UI 线程)。如果不是,则需要通过 Invoke 或 BeginInvoke 调度到 UI 线程。
  • Invoke:同步执行指定的操作,等待操作完成后再继续。
  • BeginInvoke:异步执行指定的操作,不阻塞当前线程。

 

输出效果:
点击按钮后,label 的文本会每 500 毫秒更新一次,显示当前计数值。


2. 使用 Dispatcher.Invoke 或 Dispatcher.BeginInvoke(WPF)

在 WPF 应用程序中,可以使用 Dispatcher 对象将操作调度到 UI 线程。

示例代码:

using System;
using System.Reflection.Metadata;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using static System.Net.Mime.MediaTypeNames;

class MainWindow : Window
{
    private Button button;
    private TextBlock textBlock;

    public MainWindow()
    {
        button = new Button { Content = "Start Thread" };
        textBlock = new TextBlock { Text = "Waiting..." };

        button.Click += Button_Click;

        var stackPanel = new StackPanel();
        stackPanel.Children.Add(button);
        stackPanel.Children.Add(textBlock);

        Content = stackPanel;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Thread thread = new Thread(UpdateTextBlock);
        thread.Start();
    }

    private void UpdateTextBlock()
    {
        for (int i = 0; i < 10; i++)
        {
            // 使用 Dispatcher 将操作调度到 UI 线程
            textBlock.Dispatcher.Invoke(() => textBlock.Text = $"Count: {i}");

            Thread.Sleep(500); // 模拟工作
        }
    }
}

class Program
{
    [STAThread]
    static void Main()
    {
        var app = new Application();
        app.Run(new MainWindow());
    }
}

解释:

  • Dispatcher.Invoke:同步执行指定的操作,确保操作在 UI 线程上运行。
  • Dispatcher.BeginInvoke:异步执行指定的操作,不阻塞当前线程。

输出效果:

点击按钮后,TextBlock 的文本会每 500 毫秒更新一次,显示当前计数值。


3. 使用 SynchronizationContext

SynchronizationContext 是一种更通用的方式,适用于 Windows Forms 和 WPF,甚至其他框架(如 ASP.NET)。它允许你捕获当前线程的上下文,并在需要时将其用于调度操作。

示例代码:

using System;
using System.Reflection.Emit;
using System.Threading;
using System.Windows.Forms;
using static System.Net.Mime.MediaTypeNames;

class Program : Form
{
    private Button button;
    private Label label;

    private SynchronizationContext _uiContext;

    public Program()
    {
        button = new Button { Text = "Start Thread", Dock = DockStyle.Top };
        label = new Label { Text = "Waiting...", Dock = DockStyle.Fill };

        button.Click += Button_Click;

        Controls.Add(label);
        Controls.Add(button);

        // 捕获 UI 线程的上下文
        _uiContext = SynchronizationContext.Current;
    }

    private void Button_Click(object sender, EventArgs e)
    {
        Thread thread = new Thread(UpdateLabel);
        thread.Start();
    }

    private void UpdateLabel()
    {
        for (int i = 0; i < 10; i++)
        {
            // 使用 SynchronizationContext 将操作调度到 UI 线程
            _uiContext.Post(_ => label.Text = $"Count: {i}", null);

            Thread.Sleep(500); // 模拟工作
        }
    }

    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.Run(new Program());
    }
}

解释:

  • SynchronizationContext.Current:捕获当前线程的上下文(通常是 UI 线程的上下文)。
  • Post:异步执行指定的操作。
  • Send:同步执行指定的操作。

输出效果:

点击按钮后,label 的文本会每 500 毫秒更新一次,显示当前计数值。

 

 

 

http://www.dtcms.com/a/122595.html

相关文章:

  • 学习MySQL的第六天
  • vue+uniapp 获取上一页直接传递的参数
  • 大数据(6)【Kettle入门指南】从零开始掌握ETL工具:基础操作与实战案例解析
  • Spring Boot 自定义配置类(包含字符串、数字、布尔、小数、集合、映射、嵌套对象)实现步骤及示例
  • PHP 表单处理详解
  • docker安装软件汇总(持续更新)
  • 2022年全国职业院校技能大赛 高职组 “大数据技术与应用” 赛项赛卷(2卷)任务书
  • (三)行为模式:12、访问者模式(Visitor Pattern)(C++示例)
  • 家居实用品:生活中的艺术,家的温馨源泉‌
  • skynet.dispatch 使用详解
  • 微信小程序中的openid的作用
  • 对比 redis keys 命令 ,下次面试说用 scan
  • Python-Django+vue宠物服务管理系统功能说明
  • 如何在powerbi使用自定义SQL
  • 自定义控件封装
  • 【QT】QT编译链接 msql 数据库
  • vue用D3.js实现轮盘抽奖
  • AC 自动机 洛谷P3808 P3796 P5357
  • 深度学习篇---LSTMFFTGCT
  • CSV文件读取文件表头字符串含ZWNBSP(零宽度空白字符)
  • Python第八章02:数据可视化Pyecharts包无法使用
  • 【scikit-learn基础】--『预处理』之 数据缩放
  • telophoto源码查看记录 二
  • jmeter插件安装
  • 蓝桥杯备考
  • 【问题排查】SQLite安装失败
  • 五、Linux的使用和操作(2)
  • clickhouse注入手法总结
  • 13.支持 RESTful
  • 请你说一说测试用例的边界