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線程調用的情況。
