WPF&MVVM執行緒問題(progressbar為例)
WPF&MVVM執行緒問題
別讓能力撐不起野心
後臺,UI執行緒
專業解釋我就不貼,說說自己的個人愚見,執行緒有後臺,UI(前臺)之分,UI元素所使用的執行緒為UI執行緒,其他的可以理解為後臺執行緒。
區別:程式要關閉,必須等待UI執行緒終止,而不用等待後臺執行緒終止。(這個也是為什麼有時候我們的介面會卡死,但也關閉不了的原因)
舉個例子:
介面上做個按鈕ProgressBar,和Button,按鈕click設定點選事件:
private void Button_Click(object sender, RoutedEventArgs e)
{
for (int j = 0; j < 100; j++)
{
this.progressbar.Value = j;
System.Threading.Thread.Sleep(100);
}
}
按鈕點選後,我們會發現視窗卡住了,今天我們就是要處理這樣的問題。
一般執行緒互動
有了上面拋的磚,下面我們繼續撿磚(常規WPF的執行緒互動)。
先說說所使用的技術:
1.Dispatcher(UI),執行緒排程器;
2.Task.Factory,(執行緒工廠,底層是執行緒池);
a.首次修改(引入Dispatcher)
直接上程式碼了
private void Button_Click(object sender, RoutedEventArgs e)
{
for (int j = 0; j < 100; j++)
{
this.progressbar.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background, (Action)delegate () {
this.progressbar .Value = j;
});
System.Threading.Thread.Sleep(100);
}
}
這裡通過this.progressbar.Dispatcher來獲取UI執行緒的排程器,然後使用非同步方法更新UI,
關於Dispatcher的用法可以參考這裡Dispatcher
實際上是,這裡並沒有起任何作用,那麼就有疑問:為什麼我呼叫了UI執行緒更新UI卻不起作用。
原因在於,我們這個事件的擁有者是sender,而這裡的sender就是Button,UI元素,其實這裡本身就已經是UI執行緒,也是主執行緒。
b.再次修改(引入Task)
老規矩,先上程式碼:
private void Button_Click(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() =>
{
for (int j = 0; j < 100; j++)
{
this.progressbar.Value = j;
System.Threading.Thread.Sleep(100);
}
});
}
這裡加入了Task.Factory的用法,關於Task.Factory的用法可以參考這裡Task.Factory
編譯通過,可是執行時報錯,在 this.progressbar.Value = j 時報
“呼叫執行緒無法訪問此物件,因為另一個執行緒擁有該物件。”
我們startnew了一個新執行緒,但是在這個執行緒中操作UI物件(不是UI執行緒,這裡要注意),物件j傳遞出錯。
c.最後修改
聰明的人應該已經知道接下來就是兩者一起運用了:
private void Button_Click(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() =>
{
for (int j = 0; j < 100; j++)
{ this.progressbar.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background, (Action)delegate () { this.progressbar.Value = j; });
System.Threading.Thread.Sleep(100);
}
});
}
這裡結合使用,具體的意思是:在新執行緒(後臺執行緒)中進行執行運算(sleep(100)模擬運算),在UI執行緒進行小更新(progressbar),各司其職,合理分配。
MVVM執行緒互動
關於mvvm的使用,已經不是新鮮事了,但是系統的mvvm的教程還是不多,而且一般都是邊開發邊學習得來的知識,包括自己,難免會缺三補四。
關於MVVM模式的說明和使用,個人推薦一下msdn說明和devexpress應用&程式碼
吐槽的話少說,上硬菜。
1.mvvm的濫用:
為什麼這麼說,自己之前的一個愚見,通過在ViewModel中設定相關的屬性變數,命令command,事件event,然後讓View的控制元件進行繫結,通過後臺修改ViewModel變數的屬性,實現更新UI。這樣一來,介面增加控制元件,ViewModel增加相應的變數,命令,事件,而不用再View的cs後臺進行修改。
初期,這是個還可以接受的做法,到了後期,會發現,這個ViewModel十分的龐大,維繫成本增加,要是程式碼轉接,這也是一個需要學習瞭解的過程。(關於這點,還沒有學習到相關的資料,只是自己想象減輕工作的思路,自己也是這麼做的,就是介面控制元件化,介面拆分成細緻的控制元件,讓控制元件獨立繫結ViewModel,今天問題不在這裡)
2.ViewModel不方便的地方:
ViewModel可以實現絕大部分的介面互動,不過,總會有些奇奇怪怪的功能讓你懊惱。比如:按鈕實現窗體退出功能,子窗體跳轉功能,部分控制元件的部分屬性修改。
這些功能,對於winform老手,沒有多少選擇,直接在View後臺幾行程式碼就實現了,而選擇了使用MVVM,讓窗體的closed屬性bignding,這個還真沒有試過吧(其實窗體還沒有closed屬性,只有closed事件)。
3.MVVM執行緒互動:
以上兩點純屬是自己開發過程中的一些牢騷總結,回來正式話題。
ViewModel中的Command實際上是後臺執行緒,而非UI執行緒,與click事件之類的事件不同,而bingding的屬性都會有一個PropertyChanged的方法通知View,它控制元件bingding的這個屬性改變了,讓控制元件重新整理資料,這裡實質上是UI執行緒的互動,相關理論知識還得檢視INotifyPropertyChanged介面描述。
現在的問題來了,如果我一個控制元件,沒有實現bingding,但我想操作它的屬性,該怎麼辦,這個就是本章的主要問題,如果你正在做相關的工作,從這裡直接看就可以了。
所使用的技術點:
1.EventHandler;
2.Dispatcher(同上描述);
ViewModel的程式碼:
public event EventHandler CloseProgressBarEvent;
public event EventHandler CloseWndAndShowGameView;
public ICommand PlusCount { get; set; }
…………
public MainViewModel()
{
…………
this.PlusCount = new DelegateCommand(this.plusCount);
…………
}
private void plusCount()
{
…………
var dispatcher = App.Current.MainWindow.Dispatcher;
dispatcher.BeginInvoke((Action)delegate () { CloseProgressBarEvent?.Invoke(this, tmpArgs); });
Task.Factory.StartNew(() =>
{
ShowValue = 0;
System.Threading.Thread.Sleep(2000);
while (ShowValue < 100)
{
ShowValue += 1;
System.Threading.Thread.Sleep(100);
tmpArgs._message = $"the program precent is {ShowValue.ToString()}";
dispatcher.BeginInvoke((Action)delegate () { CloseProgressBarEvent?.Invoke(this, tmpArgs); });
}
dispatcher.Invoke((Action)delegate () { CloseWndAndShowGameView?.Invoke(this, new EventArgs()); });
});
}
這裡關鍵的程式碼就是var dispatcher = App.Current.MainWindow.Dispatcher獲取當前視窗執行緒,讓窗體執行緒觸發EventHandler的操作,下面來看看EventHandler 的程式碼:
public MainWindow()
{
InitializeComponent();
var vm = new MainViewModel();
vm.CloseProgressBarEvent += Vm_CloseProgressBarEvent;
vm.CloseWndAndShowGameView += Vm_CloseWndAndShowGameView; ;
this.DataContext = vm;
}
private void Vm_CloseWndAndShowGameView(object sender, System.EventArgs e)
{
GameView newWnd = new GameView();
newWnd.Show();
this.Close();
}
private void Vm_CloseProgressBarEvent(object sender, System.EventArgs e)
{
MyEventArgs tmp = e as MyEventArgs;
this.textBlock.Text = tmp._message;
}
大家先別吐槽,這event是有點粗暴,但是,能讓你一眼就看懂的程式碼,也就這樣了,對,就是這麼簡單粗暴。直接的使用this來操作UI介面。
這裡也使用到了Task.Factory,Dispatcher的非同步,同步方法,這就是我們一般執行緒互動提到的方法。
原始碼下載
最後,原始碼在這裡Dispatcher。
最近發現CSDN資源已經不能免費了,在這裡補充百度雲的連結:Dispatcher。
本文最後修改時間:2017年8月12日16:10:11。