• C# WPF Threads 和 Dispatchers 有什么区别


    在C# WPF(Windows Presentation Foundation)中,Threads(线程)和Dispatchers(调度器)之间的关系非常重要,因为WPF是一个基于STA(单线程单元)的UI框架。

    Threads(线程)

    线程是操作系统能够进行运算调度的最小单位。它被包含在进程中,是进程中的实际运作单位。在.NET中,可以通过System.Threading.Thread类来创建和控制线程。

    Dispatchers(调度器)

    WPF中的Dispatcher对象是用来管理线程的工作队列的。每个UI线程都有一个与之关联的DispatcherDispatcher的主要作用是确保线程安全,即当你想更新UI元素时,这个操作必须在拥有这些UI元素的线程上进行。在WPF中,这通常是主UI线程。

    关系

    WPF UI元素创建在哪个线程上,就只能由那个线程直接操作。这是因为WPF UI组件是不安全的线程,这意味着在没有适当同步机制的情况下,它们不能支持从多个线程的并发访问。这就是为什么WPF提供了Dispatcher

    当你想要从非UI线程(例如后台工作线程)更新UI元素时,你不能直接访问它,因为这将违反线程安全规则并可能导致应用程序崩溃。相反,你必须将更新UI的操作“调度”回UI线程。

    这是通过UI线程的Dispatcher来实现的。你可以使用InvokeBeginInvoke方法将一个委托发送给UI线程的DispatcherDispatcher将该委托加入到UI线程的消息队列中,然后当UI线程准备好时,它会执行那个委托,从而更新UI。

    这里有一个简单的使用Dispatcher的例子:

    // 假设这是在后台线程中执行的一段代码
    this.Dispatcher.Invoke(() =>
    {
        // 这里的代码会在UI线程中执行
        myLabel.Content = "更新后的标签内容";
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Invoke是同步的,意味着它会等待UI线程执行完该操作后才继续执行后台线程的代码。另一方面,BeginInvoke是异步的,它不会等待UI线程完成就继续执行。

    总结:在WPF中,Dispatcher负责协调线程之间的交互,确保UI的线程安全性。通过使用Dispatcher,开发者可以从后台线程安全地更新UI,而不会引起线程间的冲突。

    下面通过一个简单的WPF应用程序来演示如何使用Dispatcher在不同的线程上更新UI。为了简单起见,我们将创建一个窗口,其中包含一个Button和一个Label。点击Button时,我们将在一个后台线程上开始一个操作,该操作将在完成时更新Label的内容。

    首先,我们从XAML代码开始,定义窗口的界面:

    MainWindow.xaml:

    <Window x:Class="WpfApp.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="Dispatcher Example" Height="200" Width="300">
        <StackPanel>
            <Button x:Name="UpdateButton" Content="Update Label" Click="UpdateButton_Click" />
            <Label x:Name="ResultLabel" Content="Initial Content" />
        StackPanel>
    Window>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这个XAML布局中,我们有一个StackPanel包含一个名为UpdateButtonButton和一个名为ResultLabelLabelButton点击将触发UpdateButton_Click事件。

    现在,我们需要在C#代码中实现这个事件处理程序,并在其中启动一个后台线程来模拟耗时操作。完成后,我们将更新Label

    MainWindow.xaml.cs:

    using System;
    using System.Threading;
    using System.Windows;
    
    namespace WpfApp
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
    
            private void UpdateButton_Click(object sender, RoutedEventArgs e)
            {
                // 在后台线程上启动一个操作
                Thread backgroundThread = new Thread(new ThreadStart(BackgroundProcess));
                backgroundThread.Start(); // Start the background thread
            }
    
            private void BackgroundProcess()
            {
                // 模拟耗时操作
                Thread.Sleep(3000); // Wait for 3 seconds
    
                // 更新UI元素
                // 判断是否需要通过Dispatcher进行线程间操作
                if (ResultLabel.Dispatcher.CheckAccess())
                {
                    // 当前线程是创建ResultLabel的线程,可以直接更新
                    ResultLabel.Content = "Updated from background thread";
                }
                else
                {
                    // 当前线程不是创建ResultLabel的线程,使用Dispatcher
                    ResultLabel.Dispatcher.Invoke(() =>
                    {
                        // 这段代码在UI线程执行
                        ResultLabel.Content = "Updated from background thread";
                    });
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    UpdateButton_Click方法中,我们创建了一个后台线程,然后开始运行BackgroundProcess方法。在这个方法中,我们首先通过Thread.Sleep模拟耗时的操作。

    接下来,我们检查是否可以直接访问ResultLabel,如果可以,我们就直接更新它的Content。如果不可以(这是大多数情况,因为BackgroundProcess运行在不同的线程上),我们需要使用ResultLabel.Dispatcher.Invoke,这样就可以通过UI线程的Dispatcher来更新Label

    代码中使用的Dispatcher.CheckAccess方法是用来检查当前线程是否有权限直接更新UI元素。如果没有,我们必须使用Dispatcher.Invoke来确保UI元素的更新操作在正确的线程上执行。

    这样,我们就能在后台线程完成操作后安全地更新UI了,而不会引发任何线程安全问题或异常。这是在WPF中进行线程间通信并更新UI的标准做法。

  • 相关阅读:
    GPT研究
    OpenCV入门(C++/Python)-使用OpenCV裁剪图像(四)
    vscode不显示横滚动条处理
    面试十三、malloc 、calloc、realloc以及new的区别
    Day3--流媒体缓存、消息缓存(重要)、直接任务通知
    Tomcat+Maven+Servlet安装与部署
    【SwiftUI模块】0012、SwiftUI-搭建一个类似微博、网易云、抖音个人页面的头部下拉放大图片效果
    harbor私有仓库部署
    2022就业季|Spring认证教你,如何使用 Spring 构建 REST 服务(五)
    Spire.Doc for Java 11.9.5 Crack
  • 原文地址:https://blog.csdn.net/yao_hou/article/details/134431321