[转]绑定到异步的ObservableCollection
阅读原文时间:2023年07月11日阅读:1

在进行WPF开发过程中,需要从一个新的线程中操作ObservableCollection,结果程序抛出一个NotSupportedException的错误:

This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread

看其字面意思是跨线程操作不被支持。

下面的代码展示了这种错误出现的根源:

ObservableCollection users = new ObservableCollection();
public ObservableCollection 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 : ObservableCollection
{
//获取当前线程的SynchronizationContext对象
private SynchronizationContext _synchronizationContext = SynchronizationContext.Current;
public AsyncObservableCollection() { }
public AsyncObservableCollection(IEnumerable 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了。

参考文章来源:http://www.cnblogs.com/scy251147/archive/2012/10/30/2745760.html

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器