在進行WPF開發過程中,需要從一個新的線程中操作ObservableCollection,結果程序拋出一個NotSupportedException的
錯誤:
This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread
看其字面意思是跨線程操作不被支持。
下面的代碼展示了這種錯誤出現的根源:
ObservableCollection<UserListViewModel> users = new ObservableCollection<UserListViewModel>(); public ObservableCollection<UserListViewModel> Users { get { return users; } set { users = value; } }
/// <summary> /// 開啟監聽線程 /// </summary> private void openListeningThread() { isRun = true; Thread t = new Thread(new ThreadStart(() => { IPEndPoint ipEnd = new IPEndPoint(broadIPAddress, lanPort); try { while (isRun) { try { byte[] recInfo = listenClient.Receive(ref ipEnd); //接受內容,存儲到byte數組中 DealWithAcceptedInfo(recInfo); //處理接收到的數據 } catch (Exception ex) { MessageBox.Show(ex.Message); } } listenClient.Close(); isRun = false; } catch (SocketException se) { throw new SocketException(); } //捕捉試圖訪問套接字時發生錯誤。 catch (ObjectDisposedException oe) { throw new ObjectDisposedException(oe.Message); } //捕捉Socket 已關閉 catch (InvalidOperationException pe) { throw new InvalidOperationException(pe.Message); } //捕捉試圖不使用 Blocking 屬性更改阻止模式。 catch (Exception ex) { throw new Exception(ex.Message); } })); t.Start(); } /// <summary> /// 方法:處理接到的數據 /// </summary> private void DealWithAcceptedInfo(byte[] recData) { BinaryFormatter formatter = new BinaryFormatter(); MessageFlag recvMessageFlag; MemoryStream ms = new MemoryStream(recData); try { recvMessageFlag = (MessageFlag)formatter.Deserialize(ms); } catch (SerializationException e) { throw; } UserListViewModel uListViewModel = new UserListViewModel(new UserDetailModel { MyIP = recvMessageFlag.UserIP, MyName = recvMessageFlag.UserName }); switch (recvMessageFlag.Flag) { case "0x00": //這里很關鍵,當檢測到一個新的用戶上線,那么我們需要給這個新用戶發送自己的機器消息,以便新用戶能夠自動添加進列表中。 SendInfoOnline(recvMessageFlag.UserIP); if (!list.Contains(uListViewModel.MyInfo)) { list.Add(uListViewModel.MyInfo); Users.Add(uListViewModel); } break; case "0x01": //AddTextBox(, int titleOrContentFlag, int selfOrOthersFlag); //AddTextBox(string info, int titleOrContentFlag, int selfOrOthersFlag); break; case "0x02": break; case "0x03": if (list.Contains(uListViewModel.MyInfo)) { // list.Remove(uListViewModel.MyInfo); Users.Remove(uListViewModel); } break; default: break; } }
上面的方法如果在一個新的Thread中創建,就將會產生這種問題。
解決方法如下:
public class AsyncObservableCollection<T> : ObservableCollection<T> { //獲取當前線程的SynchronizationContext對象 private SynchronizationContext _synchronizationContext = SynchronizationContext.Current; public AsyncObservableCollection() { } public AsyncObservableCollection(IEnumerable<T> list) : base(list) { } protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (SynchronizationContext.Current == _synchronizationContext) { //如果操作發生在同一個線程中,不需要進行跨線程執行 RaiseCollectionChanged(e); } else { //如果不是發生在同一個線程中 //准確說來,這里是在一個非UI線程中,需要進行UI的更新所進行的操作 _synchronizationContext.Post(RaiseCollectionChanged, e); } } private void RaiseCollectionChanged(object param) { // 執行 base.OnCollectionChanged((NotifyCollectionChangedEventArgs)param); } protected override void OnPropertyChanged(PropertyChangedEventArgs e) { if (SynchronizationContext.Current == _synchronizationContext) { // Execute the PropertyChanged event on the current thread RaisePropertyChanged(e); } else { // Post the PropertyChanged event on the creator thread _synchronizationContext.Post(RaisePropertyChanged, e); } } private void RaisePropertyChanged(object param) { // We are in the creator thread, call the base implementation directly base.OnPropertyChanged((PropertyChangedEventArgs)param); } }
將上面的ObservableCollection替換掉即可。
AsyncObservableCollection<UserListViewModel> users = new AsyncObservableCollection<UserListViewModel>(); public AsyncObservableCollection<UserListViewModel> Users { get { return users; } set { users = value; } }
參考文章:[WPF] Binding to an asynchronous collection
之所以利用SynchronizationContext,我覺得是因為后台處理線程和UI進行交互的時候,沒有獲取到SynchronizationContext的狀態導致的。因為后台和前台的線程交互,需要通過SynchronizationContext的Send或者Post方法才能避免線程Exception。
有人說可以利用Control.Invoke方法來實現啊。。。實現個鬼啊,這里就沒有Control.....你只能自己來同步SynchronizationContext了。