在應用程序中,線程能夠被看作是應用程序的一個較小的執行單位。每一個應用程序都至少擁有一個線程,咱們稱爲主線程,這是在啓動時調用應用程序的主方法時由操做系統分配啓動的線程。多線程
當調用和操做主線程的時候,該操做將動做添加到一個隊列中。每一個操做均按照將它們添加到隊列中的順序連續執行,可是能夠經過爲這些動做指定優先級來影響執行順序,而負責管理此隊列的對象稱之爲線程調度程序。併發
在不少狀況下,咱們啓動新的線程主目的是執行操做(或等待某個操做的結果),而不會致使應用程序的其他部分被阻塞。密集型計算操做、高併發I/O操做等都是這種狀況,因此如今的複雜應用程序日益多線程化了。app
當咱們啓動一個應用程序並建立對象時,就會調用構造函數方法所在的線程,對於 UI 元素,在加載 XAML 文檔時,XAML 分析器會建立基於這些UI元素的對象。因此全部的對象(包括UI元素)的建立都歸屬於當前的主線程,固然也只有主線程能夠訪問他們。框架
但在實際狀況中,有不少狀況是要假手其餘線程來處理的。異步
好比在一個長交互中,咱們可能須要而外的線程來處理複雜的執行過程,以避免形成線程阻塞,給用戶界面卡死的錯覺。函數
好比下面這個例子,咱們使用委託的方式模擬用戶執行數據建立的操做:高併發
調用CreateUserInfoHelper幫助類 和 執行 CreateProcess方法 的代碼以下:工具
1 UserParam up = new UserParam() { UserAdd = txtUserAdd.Text, UserName = txtUserName.Text, UserPhone = txtUserPhone.Text, UserSex = txtUserSex.Text }; 2 CreateUserInfoHelper creatUser = new CreateUserInfoHelper(up); 3 creatUser.CreateProcess += new EventHandler<CreateUserInfoHelper.CreateArgs>(CreateProcess); //註冊事件 4 creatUser.Create(); 5 processPanel.Visibility = Visibility.Visible;
1 private void CreateProcess(object sender, CreateUserInfoHelper.CreateArgs args)//響應時間執行 2 { 3 processBar.Value = args.process; 4 processInfo.Text = String.Format("建立進度:{0}/100",args.process); 5 if (args.isFinish) 6 { 7 if (args.userInfo != null) 8 { 9 ObservableCollection<UserParam> data = (ObservableCollection<UserParam>)dg.DataContext; 10 data.Add(args.userInfo); 11 dg.DataContext = data; 12 } 13 processPanel.Visibility = Visibility.Hidden; 14 ClearForm(); 15 } 16 }
CreateUserInfoHelper幫助類代碼以下:this
1 public class CreateUserInfoHelper 2 { 3 //執行進度事件(響應註冊的事件) 4 public event EventHandler<CreateArgs> CreateProcess; 5 6 //待建立信息 7 public UserParam up { get; set; } 8 9 public CreateUserInfoHelper(UserParam _up) 10 { 11 up = _up; 12 } 13 14 public void Create() 15 { 16 Thread t = new Thread(Start);//拋出一個行線程 17 t.Start(); 18 } 19 20 private void Start() 21 { 22 try 23 { 24 //ToDo:編寫建立用戶的DataAccess代碼 25 for (Int32 idx = 1; idx <= 10; idx++) 26 { 27 CreateProcess(this, new CreateArgs() 28 { 29 isFinish = ((idx == 10) ? true : false), 30 process = idx * 10, 31 userInfo =null 32 }); 33 Thread.Sleep(1000); 34 } 35 36 CreateProcess(this, new CreateArgs() 37 { 38 isFinish = true, 39 process = 100, 40 userInfo =up 41 }); 42 } 43 catch (Exception ex) 44 { 45 CreateProcess(this, new CreateArgs() 46 { 47 isFinish = true, 48 process = 100, 49 userInfo = null 50 }); 51 } 52 } 53 54 /// <summary> 55 /// 建立步驟反饋參數 56 /// </summary> 57 public class CreateArgs : EventArgs 58 { 59 /// <summary> 60 /// 是否建立結束 61 /// </summary> 62 public Boolean isFinish { get; set; } 63 /// <summary> 64 /// 進度 65 /// </summary> 66 public Int32 process { get; set; } 67 /// <summary> 68 /// 處理後的用戶信息 69 /// </summary> 70 public UserParam userInfo { get; set; } 71 } 72 }
目的很簡單:就是在建立用戶信息的時候,使用另一個線程執行建立工做,最後將結果呈如今試圖列表上,而在這個建立過程當中會相應的呈現進度條。spa
來看下效果:
立馬報錯了,緣由很簡單,在建立對象時,該操做發生在調用CreateUserInfoHelper幫助類方法所在的線程中。
對於 UI 元素,在加載 XAML 文檔時,XAML 分析器會建立對象。全部這一切都在主線程上進行。所以,全部這些 UI 元素都屬於主線程,這也一般稱爲 UI 線程。
當先前代碼中的後臺線程嘗試修改 UI主線程的元素 屬性時,則會致使非法的跨線程訪問。所以會引起異常。
解決辦法就是去通知主線程來處理UI, 經過向主線程的Dispatcher隊列註冊工做項,來通知UI線程更新結果。
Dispatcher提供兩個註冊工做項的方法:Invoke 和 BeginInvoke。
這兩個方法均調度一個委託來執行。Invoke 是同步調用,也就是說,直到 UI 線程實際執行完該委託它才返回。BeginInvoke是異步的,將當即返回。
因此咱們修改上面的代碼以下:
1 private void CreateProcess(object sender, CreateUserInfoHelper.CreateArgs args) 2 { 3 this.Dispatcher.BeginInvoke((Action)delegate() 4 { 5 processBar.Value = args.process; 6 processInfo.Text = String.Format("建立進度:{0}/100",args.process); 7 if (args.isFinish) 8 { 9 if (args.userInfo != null) 10 { 11 ObservableCollection<UserParam> data = (ObservableCollection<UserParam>)dg.DataContext; 12 data.Add(args.userInfo); 13 dg.DataContext = data; 14 } 15 processPanel.Visibility = Visibility.Hidden; 16 ClearForm(); 17 } 18 }); 19 }
結果以下:
實現異步執行的結果。
當從 ViewModel 執行後臺操做時,狀況略有不一樣。一般,ViewModel 不從 DispatcherObject 繼承。它們是執行 INotifyPropertyChanged 接口的 Plain Old CLR Objects (POCO)。
由於 ViewModel 是一個 POCO,它不能訪問 Dispatcher 屬性,所以我須要經過另外一種方式來訪問主線程,以將操做加入隊列中。這是 MVVM Light DispatcherHelper 組件的做用。
實際上,該類所作的是將主線程的調度程序保存在靜態屬性中,並公開一些實用工具方法,以便經過便捷且一致的方式訪問。爲了實現正常功能,須要在主線程上初始化該類。
最好應在應用程序生命週期的初期進行此操做,使應用程序一開始便可以訪問這些功能。一般,在 MVVM Light 應用程序中,DispatcherHelper 在 App.xaml.cs 中進行初始化,App.xaml.cs 是定義應用程序啓動類的文件。在 Windows Phone 中,在應用程序的主框架剛剛建立以後,在 InitializePhoneApplication 方法中調用 DispatcherHelper.Initialize。在 WPF 中,該類是在 App 構造函數中進行初始化的。在 Windows 8 中,在窗口激活以後便馬上在 OnLaunched 中調用 Initialize 方法。
完成了對 DispatcherHelper.Initialize 方法的調用後,DispatcherHelper 類的 UIDispatcher 屬性包含對主線程的調度程序的引用。相對而言不多直接使用該屬性,但若是須要能夠這樣作。但最好使用 CheckBeginInvokeOnUi 方法。此方法將委託視爲參數。
因此將上述代碼改裝程:
View代碼(學過Bind和Command以後應該很好理解下面這段代碼,沒什麼特別的):
1 <Grid> 2 <Grid.Resources> 3 <Style TargetType="{x:Type Border}" x:Key="ProcessBarBorder"> 4 <Setter Property="BorderBrush" Value="LightGray" ></Setter> 5 <Setter Property="BorderThickness" Value="1" ></Setter> 6 <Setter Property="Background" Value="White" ></Setter> 7 </Style> 8 </Grid.Resources> 9 10 <!-- 延遲框 --> 11 <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" > 12 <Border Style="{StaticResource ProcessBarBorder}" Padding="5" Visibility="{Binding IsWaitingDisplay,Converter={StaticResource boolToVisibility}}" Panel.ZIndex="999" HorizontalAlignment="Center" VerticalAlignment="Center" Height="50"> 13 <StackPanel Orientation="Vertical" VerticalAlignment="Center" > 14 <ProgressBar Value="{Binding ProcessRange}" Maximum="100" Width="400" Height="5" ></ProgressBar> 15 <TextBlock Text="{Binding ProcessRange,StringFormat='執行進度:\{0\}/100'}" Margin="0,10,0,0" ></TextBlock> 16 </StackPanel> 17 </Border> 18 </Grid> 19 20 <StackPanel Orientation="Vertical" IsEnabled="{Binding IsEnableForm}" > 21 <StackPanel> 22 <DataGrid ItemsSource="{Binding UserList}" AutoGenerateColumns="False" CanUserAddRows="False" 23 CanUserSortColumns="False" Margin="10" AllowDrop="True" IsReadOnly="True" > 24 <DataGrid.Columns> 25 <DataGridTextColumn Header="學生姓名" Binding="{Binding UserName}" Width="100" /> 26 <DataGridTextColumn Header="學生家庭地址" Binding="{Binding UserAdd}" Width="425" > 27 <DataGridTextColumn.ElementStyle> 28 <Style TargetType="{x:Type TextBlock}"> 29 <Setter Property="TextWrapping" Value="Wrap"/> 30 <Setter Property="Height" Value="auto"/> 31 </Style> 32 </DataGridTextColumn.ElementStyle> 33 </DataGridTextColumn> 34 <DataGridTextColumn Header="電話" Binding="{Binding UserPhone}" Width="100" /> 35 <DataGridTextColumn Header="性別" Binding="{Binding UserSex}" Width="100" /> 36 </DataGrid.Columns> 37 </DataGrid> 38 </StackPanel> 39 40 <StackPanel Orientation="Horizontal" Margin="10,10,10,10"> 41 <StackPanel Orientation="Vertical" Margin="0,0,10,0" > 42 <StackPanel Orientation="Horizontal" Margin="0,0,0,5" > 43 <TextBlock Text="學生姓名" Width="80" ></TextBlock> 44 <TextBox Text="{Binding User.UserName}" Width="200" /> 45 </StackPanel> 46 <StackPanel Orientation="Horizontal" Margin="0,0,0,5"> 47 <TextBlock Text="學生電話" Width="80" ></TextBlock> 48 <TextBox Text="{Binding User.UserPhone}" Width="200" /> 49 </StackPanel> 50 <StackPanel Orientation="Horizontal" Margin="0,0,0,5"> 51 <TextBlock Text="學生家庭地址" Width="80"></TextBlock> 52 <TextBox Text="{Binding User.UserAdd}" Width="200"/> 53 </StackPanel> 54 <StackPanel Orientation="Horizontal" Margin="0,0,0,5" > 55 <TextBlock Text="學生性別" Width="80" ></TextBlock> 56 <TextBox Text="{Binding User.UserSex}" Width="200" /> 57 </StackPanel> 58 <StackPanel> 59 <Button Content="提交" Width="100" Command="{Binding AddRecordCmd}" ></Button> 60 </StackPanel> 61 </StackPanel> 62 </StackPanel> 63 64 </StackPanel> 65 </Grid>
ViewModel代碼:
(先初始化 DispatcherHelper,再調用 CheckBeginInvokeOnUI 方法來實現對UI線程的調度)
1 public class DispatcherHelperViewModel:ViewModelBase 2 { 3 /// <summary> 4 /// 構造行數 5 /// </summary> 6 public DispatcherHelperViewModel() 7 { 8 InitData(); 9 DispatcherHelper.Initialize(); 10 } 11 12 13 #region 全局屬性 14 15 private ObservableCollection<UserParam> userList; 16 /// <summary> 17 /// 數據列表 18 /// </summary> 19 public ObservableCollection<UserParam> UserList 20 { 21 get { return userList; } 22 set { userList = value; RaisePropertyChanged(() => UserList); } 23 } 24 25 private UserParam user; 26 /// <summary> 27 /// 當前用戶信息 28 /// </summary> 29 public UserParam User 30 { 31 get { return user; } 32 set { user = value; RaisePropertyChanged(()=>User); } 33 } 34 35 36 private Boolean isEnableForm; 37 /// <summary> 38 /// 是否表單可用 39 /// </summary> 40 public bool IsEnableForm 41 { 42 get { return isEnableForm; } 43 set { isEnableForm = value; RaisePropertyChanged(()=>IsEnableForm); } 44 } 45 46 private Boolean isWaitingDisplay; 47 /// <summary> 48 /// 是都顯示延遲旋轉框 49 /// </summary> 50 public bool IsWaitingDisplay 51 { 52 get{ return isWaitingDisplay; } 53 set{ isWaitingDisplay = value; RaisePropertyChanged(()=>IsWaitingDisplay);} 54 } 55 56 private Int32 processRange; 57 /// <summary> 58 /// 進度比例 59 /// </summary> 60 public int ProcessRange 61 { 62 get { return processRange; } 63 set { processRange = value; RaisePropertyChanged(()=>ProcessRange);} 64 } 65 66 #endregion 67 68 69 #region 全局命令 70 private RelayCommand addRecordCmd; 71 /// <summary> 72 /// 添加資源 73 /// </summary> 74 public RelayCommand AddRecordCmd 75 { 76 get 77 { 78 if (addRecordCmd == null) addRecordCmd = new RelayCommand(()=>ExcuteAddRecordCmd()); 79 return addRecordCmd; 80 } 81 set 82 { 83 addRecordCmd = value; 84 } 85 } 86 #endregion 87 88 89 #region 輔助方法 90 /// <summary> 91 /// 初始化數據 92 /// </summary> 93 private void InitData() 94 { 95 UserList = new ObservableCollection<UserParam>() 96 { 97 new UserParam(){ UserName="周杰倫", UserAdd="周杰倫地址", UserPhone ="88888888", UserSex="男" }, 98 new UserParam(){ UserName="劉德華", UserAdd="劉德華地址", UserPhone ="88888888", UserSex="男" }, 99 new UserParam(){ UserName="劉若英", UserAdd="劉若英地址", UserPhone ="88888888", UserSex="女" } 100 }; 101 User = new UserParam(); 102 IsEnableForm = true; 103 IsWaitingDisplay = false; 104 } 105 106 /// <summary> 107 /// 執行命令 108 /// </summary> 109 private void ExcuteAddRecordCmd() 110 { 111 UserParam up = new UserParam { UserAdd = User.UserAdd, UserName = User.UserName, UserPhone = User.UserPhone, UserSex = User.UserSex }; 112 CreateUserInfoHelper creatUser = new CreateUserInfoHelper(up); 113 creatUser.CreateProcess += new EventHandler<CreateUserInfoHelper.CreateArgs>(CreateProcess); 114 creatUser.Create(); 115 IsEnableForm = false; 116 IsWaitingDisplay = true; 117 } 118 119 /// <summary> 120 /// 建立進度 121 /// </summary> 122 /// <param name="sender"></param> 123 /// <param name="args"></param> 124 private void CreateProcess(object sender, CreateUserInfoHelper.CreateArgs args) 125 { 126 DispatcherHelper.CheckBeginInvokeOnUI(() => 127 { 128 if (args.isFinish) 129 { 130 if (args.userInfo != null) 131 { 132 UserList.Add(args.userInfo); 133 } 134 135 IsEnableForm = true; 136 IsWaitingDisplay = false; 137 } 138 else 139 { 140 ProcessRange = args.process; 141 } 142 }); 143 } 144 #endregion 145 146 }
結果以下:
轉載請註明出處,謝謝