zoukankan      html  css  js  c++  java
  • WPF在子线程中更新控件

    2021-01-25

    关键字:子线程调用主线程资源、子线程更新UI


    WPF中想在子线程中操作在主线程中创建的控件其实很简单,使用 Dispatcher 类对象即可实现需求。

    下面直接上一个最简单的实例。

    假设我们有一个Window,里面包含了一个TextBlock控件,其界面及xaml代码如下所示:

    <Window x:Name="hello__net_core" x:Class="Wpf_dotnetonly.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:Wpf_dotnetonly"
            mc:Ignorable="d"
            Title="MainWindow" Height="150" Width="310">
        <StackPanel>
            <TextBlock x:Name="TB" Width="100" Height="50" Background="AliceBlue" />
        </StackPanel>
    </Window>

    这个程序想实现的功能是:有一个间隔一秒永久循环运行的子线程,记录这个子线程的循环次数并使其显示在TextBlock上。

    常见的做法是将对控件更新的操作封装到一个方法中,然后使用Dispatcher类对象来从子线程中更新主线程中的控件,关键代码如下所示:

        public partial class MainWindow : Window
        {
            private delegate void MyDelegate(int value);
            private int GlobalCounter;
    
            public MainWindow()
            {
                InitializeComponent();
    
                new Thread(SubThreadProc).Start();
            }
    
            private void SubThreadProc()
            {
                int counter = 0;
                
                while(true)
                {
                    counter++;
                    GlobalCounter++;
                    Thread.Sleep(1000);
                    Dispatcher.Invoke(new MyDelegate(SetTB), counter);
                    //Dispatcher.Invoke(SetTB2);
              //TB.Dispatcher.Invoke(SetTB2); } }
         //方式一
    private void SetTB(int val) { TB.Text = val.ToString(); }
    //方式二
    private void SetTB2() { TB.Text = GlobalCounter.ToString(); } }

    对于无参的封装方法,直接通过Invoke()传入方法名即可。有参的封装方法,则需要通过一个Delegate来额外包装一下。

    知晓了术以后,接下来简单分析一下它的法。

    Dispatcher类位于 System.Windows.Threading 名称空间中,它的功能是为线程提供一个作业队列。在这里我们简单地将它理解成是将要执行的作业先提交到一个队列中,然后再由这个Dispatcher对象的所有者来执行这些作业即可。通常情况下,WPF中是由主线程--即UI线程负责创建Dispatcher对象的,再加上Dispatcher对象全局共享的特性,我们就可以在子线程中将作业提交到它的队列里,待Dispatcher对象的所有者--即主线程来最终执行从而达到在子线程中更新控件内容的目的。

    由于笔者没有阅读.NET CORE的源码,所以并不知道Dispatcher对象的创建流程。不过我们可以从Window以及TextBlock的继承关系来大概猜测一下,以下猜测很可能是错的,仅供娱乐,切勿当真。

    Window的继承关系如下所示:

    DispatcherObject
      ^
    DependencyObject
      ^ Visual
      ^ UIElement
      ^ FrameworkElement
      ^ Control
      ^ ContentControl
      ^ Window

    TextBlock的继承关系如下所示:

    DispatcherObject
        ^
    DependencyObject
        ^
    Visual
        ^
    UIElement
        ^
    FrameworkElement
        ^
    TextBlock

    Dispacthcer类就是DispactherObejct类中的属性。由此可以猜想,Dispatcher类是在控件或窗口实例化的时候就创建了的。同时又因为Dispacther类对象是全局唯一且共享的,进一步提出是在WPF程序启动时创建的。猜测结束!

    一般来说,Dispatcher对象我们只管用即可,不需要去理会它的生命周期,它不需要我们去操心创建、管理或销毁。事实上,一个线程有且只能有一个Dispatcher对象,且一旦一个Dispatcher对象被销毁以后就不能再恢复了。换句话说,如果我们不小心将主线程的Dispatcher对象注销掉了,那就只有重启程序才能重新创建了。

    言归正传,我们还是重点来看看Dispatcher关于在子线程中操作主线程控件的方法。

    Dispatcher提供了很多用于此目的的方法,但概括下来主要有两种:

      1、同步式的;

      2、异步式的。

    1、同步式的

    Invoke为方法名的各种重载方法。本文介绍以下几种典型方法,更详尽的请参阅官方文档:

    https://docs.microsoft.com/en-us/dotnet/api/system.windows.threading.dispatcher.invoke?view=net-5.0#System_Windows_Threading_Dispatcher_Invoke_System_Action_

    a), public void Invoke (Action callback);

    最简单的。在子线程中调用该方法后会阻塞直至主线程将Action代理中的方法执行完成为止。Action的原型为一个无参代理函数:public delegate void Action(); 一般直接传方法名称即可。

    b), public object Invoke (Delegate method, params object[] args);

    有参的封装方法。参数 method 是一个开放的Delegate类型,你可以任意定义Delegate并将其封装后传入,参数args则是要传递给method的参数列表。这个方法的使用正如本文开头的例子所示。

    c), public object Invoke (Delegate method, System.Windows.Threading.DispatcherPriority priority, params object[] args);

    与b)类似,不同的是允许为要执行的作业设置优先级。优先级为一个枚举类,该枚举类数值越大优先级越高。具体请参阅官方文档:https://docs.microsoft.com/en-us/dotnet/api/system.windows.threading.dispatcherpriority?view=net-5.0

    没有特殊的要求都不需要去设置优先级。

    2、异步式的

    BeginInvoke为方法名的重载方法。该方法调用后会立即返回,提交的作业会在恰当的时机被Dispatcher的持有线程执行。

    a), public System.Windows.Threading.DispatcherOperation BeginInvoke (Delegate method, params object[] args);

    带参数的封装方法。

    b), public DispatcherOperation BeginInvoke (Delegate method, DispatcherPriority priority, params object[] args);

    带参数且可以指定优先级的封装方法。

    到此,本文所介绍的知识基本够用了,如果想更进一步了解Dispacther,请参阅官方文档:

    https://docs.microsoft.com/en-us/dotnet/api/system.windows.threading.dispatcher?view=net-5.0


    +++
  • 相关阅读:
    面向对象(2)
    毕业季面试题(7)
    面向对象(class0420)
    ASP.NET入门(class0612)
    数据结构与算法(二叉树)
    算法总结(2)数据结构
    毕业季面试题(6)
    常规页生命周期(class0620)
    (三) 语句
    (二) 运算符
  • 原文地址:https://www.cnblogs.com/chorm590/p/14305380.html
Copyright © 2011-2022 走看看