1. 程式人生 > >利刃 MVVMLight 8:DispatchHelper在多線程和調度中的使用

利刃 MVVMLight 8:DispatchHelper在多線程和調度中的使用

isf ref ping 輔助 比例 修改 brush ner aac

在應用程序中,線程可以被看做是應用程序的一個較小的執行單位。每個應用程序都至少擁有一個線程,我們稱為主線程,這是在啟動時調用應用程序的主方法時由操作系統分配啟動的線程。

當調用和操作主線程的時候,該操作將動作添加到一個隊列中。每個操作均按照將它們添加到隊列中的順序連續執行,但是可以通過為這些動作指定優先級來影響執行順序,而負責管理此隊列的對象稱之為線程調度程序。

在很多情況下,我們啟動新的線程主目的是執行操作(或等待某個操作的結果),而不會導致應用程序的其余部分被阻塞。密集型計算操作、高並發I/O操作等都是這種情況,所以現在的復雜應用程序日益多線程化了。

當我們啟動一個應用程序並創建對象時,就會調用構造函數方法所在的線程,對於 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幫助類代碼如下:

 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     }

目的很簡單:就是在創建用戶信息的時候,使用另外一個線程執行創建工作,最後將結果呈現在試圖列表上,而在這個創建過程中會相應的呈現進度條。

來看下效果:

技術分享

立馬報錯了,原因很簡單,在創建對象時,該操作發生在調用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         }

結果如下:

技術分享

技術分享

實現異步執行的結果。

MVVM 應用程序中的調度

當從 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 方法中調用 Dispatcher­Helper.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     }

結果如下:

技術分享

示例代碼下載

轉載請註明出處,謝謝

利刃 MVVMLight 8:DispatchHelper在多線程和調度中的使用