• DispatcherHelper


    DispatcherHelper

    通常,WPF 应用程序从两个线程开始:一个用于处理呈现,
    一个用于管理 UI。呈现线程有效地隐藏在后台运行,而 UI 线程则接收输入、处理事件、绘制屏幕
    以及运行应用程序代码。所以我们的大多数操作都会在UI线程中执行,同时它也处理绘制屏幕,如果我们
    的一个操作相当耗时,那么它就没有机会处理绘制屏幕,此时我们是不能够拖动窗口的,也就是通常
    说的屏幕卡住了。

    Dispatcher,DispatcherObject,DependencyObject

    Dispatcher即调度器,我们可以把UI线程看作CPU,我们的每个操作就是指令,指令发送到CPU处理,
    CPU同时只能处理一个指令,当指令非常多时,优先级高的指令要先处理,低的可以稍后处理,Dispatcher
    就是用于处理这些操作的,它将我们的操作根据优先级排队,等待UI线程来处理。

    在WPF中,所有的WPF对象都派生自DispatcherObject,
    DispatcherObject暴露了Dispatcher属性用来取得创建对象线程对应的Dispatcher。
    鉴于线程亲缘性,DispatcherObject对象只能被创建它的线程所访问,
    其他线程修改DispatcherObject需要取得对应的Dispatcher,
    调用Invoke或者BeginInvoke来投入任务。
    一个UI线程至少有一个Dispatcher来建立消息泵处理任务,一个Dispatcher只能对应一个UI线程。

    WPF中的控件都是主UI线程创建的,也就是只有主UI线程可以访问他们,如果是其他线程访问控件
    那么就会报错

            Task.Factory.StartNew(() =>
            {
                btn.Content = "HelloWorld";
            });
    

    跨线程错误

    解决办法就是在主线程中访问他们,或是

    Task.Factory.StartNew(() =>
            {
                //耗时操作
                Thread.Sleep(5000);
    
                //最后结果通过主线程更新到控件
                btn.Dispatcher.Invoke(() =>
                {
                    btn.Content = "HelloWorld";
                });
                
            });
    

    在WPF中,Dispatcher,DispatcherObject和DependencyObject决定了一个对象的线程亲缘性,这里提供一个方便查看源代码的网址
    我们首先查看Dispatcher的源码,发现两个比较重要的方法和一个静态属性

    Dispatcher
    |____CurrentDispatcher
    |____CheckAccess()
    |____VerifyAccess()

    CurrentDispatcher获取当前线程的Dispatcher,如果当前线程没有Dispatcher,那么就创建一个
    CheckAccess()方法用于判断,当前Dispatcher所属的线程是不是当前线程
    VerifyAccess()其实就是调用的CheckAccess,如果Dispatcher不属于当前线程,那么就报异常,这就是上面图片所示的异常

    我们在来看DispatcherObject,看到一个构造函数,2个方法和一个属性

    DispatcherObject
    |____DispatcherObject
    |____Dispatcher
    |____CheckAccess()
    |____VerifyAccess()

    当创建一个DispatcherObject对象的时候,会给这个对象分配一个当前线程的Dispatcher,其他两个方法也就是对Dispatcher方法的封装

    好,最后我们来看DependencyObject,看到两个重要的方法GetValue,SetValue

    DependencyObject
    |____SetValue(DependencyProperty dp,object value)
    |____GetValue(DependencyProperty dp)

    这两个方法中的的第一句就是this.VerifyAccess(),而DependencyObject是直接继承自DispatcherObject的,其实就是使用的Dispatcher的VerifyAccess,到此
    WPF对象的线程亲缘性已经很明了了,所以,一个对象只能被它所创建的线程所访问。

    Binding的源在多线程中怎么处理

    网上比较好的文章

    我的理解

    我说下我的理解,我们在ViewModel中更新数据都要触发OnPropertyChanged事件,这个事件其实是被
    一个叫做PropertyChangedEventManager的家伙订阅的,它又会去触发相应控件的OnPropertyChanged事件
    ,都是事件,事件也是方法,在哪个线程促发的事件,就在哪个线程处理,照这么说,我们在非UI线程
    更新Model的属性,那么对应的会在非UI线程处理与之相绑定的控件,这里就会报如上的错误。我们做个
    实验来测试下是不是这样的。

    实验

    界面上显示一个Teacher的基本数据,Name,Age,还有所管理的学生Student,我们在非UI线程做
    以下操作

    • 修改Teacher的属性
    • 增加学生数
    • 修改最后一名学生的姓名

    Teacher.cs

    public class Teacher : ObservableObject
    {
        private string _name;
        private int _age;
        private ObservableCollection<Student> _students;
    
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
                RaisePropertyChanged(() => Name);
            }
        }
    
        public int Age
        {
            get
            {
                return _age;
            }
            set
            {
                _age = value;
                RaisePropertyChanged(() => Age);
            }
        }
    
        public ObservableCollection<Student> Students
        {
            get
            {
                return _students;
            }
            set
            {
                _students = value;
                RaisePropertyChanged(() => Students);
            }
        }
    }
    

    Student.cs

    public class Student : ObservableObject
    {
        private string _name;
        private int _age;
    
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
                RaisePropertyChanged(() => Name);
            }
        }
    
        public int Age
        {
            get
            {
                return _age;
            }
            set
            {
                _age = value;
                RaisePropertyChanged(() => Name);
            }
        }
    }
    

    MainView.xaml

        <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
    
        <StackPanel Grid.Column="0">
            <TextBlock Text="{Binding Teacher.Name}"></TextBlock>
            <TextBlock Text="{Binding Teacher.Age}"></TextBlock>
            <ListView ItemsSource="{Binding Teacher.Students}">
                <ListView.View>
                    <GridView>
                        <GridViewColumn DisplayMemberBinding="{Binding Name}"></GridViewColumn>
                        <GridViewColumn DisplayMemberBinding="{Binding Age}"></GridViewColumn>
                    </GridView>
                </ListView.View>
            </ListView>
        </StackPanel>
    
        <StackPanel Grid.Column="1">
            <Button Content="改变教师名称" Command="{Binding ChangeTeacherNameCommand}"></Button>
            <Button Content="增加学生" Command="{Binding AddStudentCommand}"></Button>
            <Button Content="改变最后一名学生名称" Command="{Binding ChangeLastStudentNameCommand}"></Button>
        </StackPanel>
    </Grid>
    

    MainViewModel.cs

    public class MainViewModel : ViewModelBase
    {
        private Teacher _teacher;
    
        public Teacher Teacher
        {
            get
            {
                return _teacher;
            }
            set
            {
                _teacher = value;
                RaisePropertyChanged(() => Teacher);
            }
        }
    
        public RelayCommand ChangeTeacherNameCommand
        {
            get; set;
        }
    
        public RelayCommand AddStudentCommand
        {
            get; set;
        }
    
        public RelayCommand ChangeLastStudentNameCommand
        {
            get; set;
        }
    
        /// <summary>
        /// Initializes a new instance of the MainViewModel class.
        /// </summary>
        public MainViewModel()
        {
            Teacher = new Teacher()
            {
                Name = "LaoZhao",
                Age = 30,
                Students = new ObservableCollection<Student>()
                {
                    new Student()
                    {
                        Name="LaoZhange",
                        Age = 18
                    }
                }
            };
    
            InitCommand();
        }
    
        private void InitCommand()
        {
            ChangeTeacherNameCommand = new RelayCommand(() =>
              {
                  Task.Factory.StartNew(() =>
                  {
                      Teacher.Name = "MaYun";
                  });
              });
    
            AddStudentCommand = new RelayCommand(() =>
              {
                  Task.Factory.StartNew(() =>
                  {
                      Teacher.Students.Add(new Student()
                      {
                          Name = "LaoLi",
                          Age = 25
                      });
                  });
              });
    
            ChangeLastStudentNameCommand = new RelayCommand(() =>
              {
                  Task.Factory.StartNew(() =>
                  {
                      var student = Teacher.Students.LastOrDefault();
    
                      if (student != null)
                      {
                          student.Name = "TheLast";
                      }
                  });
              });
        }
    }
    

    最后发现,在非UI线程更新Teacher的姓名和Student的姓名是没有问题的,那是因为WPF在后台强制
    使用了UI线程,然而向集合中增加一个学生却报错~(我猜测WPF针对有些控件不会帮我们切换到UI主线程),为了避免这些情况,如果要更新Model的属性,
    请在UI线程中。但是我们都是在ViewModel中,并不知道任何一个控件,自然也没有办法拿到Dispatcher
    好了,主角终于登场了DispatcherHelper

    DispatcherHelper

    DispatcherHelper的使用首先要初始化用来保存主线程的Dispatcher,这个Dispatcher即创建界面的
    线程,哪个线程创建的控件,只能由那个线程才能访问。所以我们一般在App的构造函数中初始化DispatcherHelper

        public App()
        {
            DispatcherHelper.Initialize();
        }
    

    DispatcherHelper主要成员以下:

    • UIDispatcher 属性:当DispatcherHelper调用Initialize方法时,使用当前线程的Dispatcher
    • Initialize 方法:初始化DispatcherHelper,并保存当前线程的Dispatcher
    • CheckBeginInvokeOnUI 方法:如果当前线程为非UI线程,那么在UI线程异步执行该方法,如果为UI
      线程,那么立即执行
    • Reset 方法:重置Dispatcher为Null
    • RunAsync 方法:在UI线程上异步执行

    所以,刚才增加学生出错的代码,我们可以修改为

        AddStudentCommand = new RelayCommand(() =>
              {
                  Task.Factory.StartNew(() =>
                  {
                      DispatcherHelper.CheckBeginInvokeOnUI(() =>
                      {
                          Teacher.Students.Add(new Student()
                          {
                              Name = "LaoLi",
                              Age = 25
                          });
                      });
                  });
              });
    

    当然,直接调用那是最好,这里只是模拟非UI线程调用的情况。

  • 相关阅读:
    J.U.C并发框架源码阅读(十五)CopyOnWriteArrayList
    J.U.C并发框架源码阅读(十四)ScheduledThreadPoolExecutor
    J.U.C并发框架源码阅读(十三)ThreadPoolExecutor
    Django基础之request对象
    Django基础之给视图加装饰器
    Django基础之初识视图
    Django基础之CBV和FBV
    Django基础之template
    Django基础之命名空间模式(include)
    Django基础之命名URL和URL反向解析
  • 原文地址:https://www.cnblogs.com/HelloMyWorld/p/4750057.html
Copyright © 2020-2023  润新知