綁定到異步的ObservableCollection


在進行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了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM