簡體   English   中英

從后台線程更新集合中的項屬性時的異常

[英]Exception when updating item property in collection from background thread

我有一個派生的ObservableCollection類,名為ItemsObservableObservableCollection。 這是因為我想綁定它並在其項目中的屬性發生變化時得到通知。

從后台線程更新項目的屬性時,它會失敗。 我想幫助解決這個問題。

該項目有兩個比較行為的集合,第一個集合是ObservableCollection類型,第二個集合是ItemsObservableObservableCollection。

MainWindow.xaml

<Window x:Class="MultiBindTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:MultiBindTest"
        Title="Multibind Test" Height="482" Width="976">

    <Window.DataContext>
        <vm:MainViewModel />
    </Window.DataContext>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Label Content="First Set - ObservableCollection" FontWeight="Bold" Grid.Row="0" HorizontalAlignment="Center" HorizontalContentAlignment="Left" />
        <ItemsControl Grid.Row="1" Width="Auto" ItemsSource="{Binding Path=ModuleCollection1}" >
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel HorizontalAlignment="Stretch" Name="WrapPanel1" VerticalAlignment="Center" />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <StackPanel Margin="10">
                        <Button Content="{Binding ModuleAbbreviation}"  
                                    Background="{Binding ModuleColor}"
                                    FontSize="32" FontFamily="Tahoma" Width="130" Height="100">
                            <Button.Resources>
                                <vm:IsEnabledMultiValueConverter x:Key="converter" />
                            </Button.Resources>
                            <Button.IsEnabled>
                                <MultiBinding Converter="{StaticResource converter}">
                                    <Binding Path="ModuleID" />
                                    <Binding Path="ModuleEnabled" />
                                    <Binding Path="ModuleLicenseDate" />
                                </MultiBinding>
                            </Button.IsEnabled>
                        </Button>
                        <Label Content="{Binding ModuleName}" FontSize="18" FontWeight="Medium" HorizontalAlignment="Center" HorizontalContentAlignment="Center" Width="210" />
                    </StackPanel>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
        <Button Content="Switch the second Module Enable/Disabled in First Set" Width="400" Grid.Row="2" Command="{Binding SwitchCommand1}" />
        <Button Content="Switch the second Module Enable/Disabled in First Set, by background thread" Width="500" Grid.Row="3" Command="{Binding SwitchCommand2}" />
        <Label Content="Second Set - ItemsObservableObservableCollection" FontWeight="Bold" Grid.Row="4" HorizontalAlignment="Center" HorizontalContentAlignment="Left" />
        <ItemsControl Grid.Row="5" Width="Auto" ItemsSource="{Binding Path=ModuleCollection2}" >
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel HorizontalAlignment="Stretch" Name="WrapPanel1" VerticalAlignment="Center" />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <StackPanel Margin="10">
                        <Button Content="{Binding ModuleAbbreviation}"  
                                    Background="{Binding ModuleColor}"
                                    FontSize="32" FontFamily="Tahoma" Width="130" Height="100">
                            <Button.Resources>
                                <vm:IsEnabledMultiValueConverter x:Key="converter" />
                            </Button.Resources>
                            <Button.IsEnabled>
                                <MultiBinding Converter="{StaticResource converter}">
                                    <Binding Path="ModuleID" />
                                    <Binding Path="ModuleEnabled" />
                                    <Binding Path="ModuleLicenseDate" />
                                </MultiBinding>
                            </Button.IsEnabled>
                        </Button>
                        <Label Content="{Binding ModuleName}" FontSize="18" FontWeight="Medium" HorizontalAlignment="Center" HorizontalContentAlignment="Center" Width="210" />
                    </StackPanel>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
        <Button Content="Switch the second Module Enable/Disabled in Second set" Width="400" Grid.Row="6" Command="{Binding SwitchCommand3}" />
        <Button Content="Switch the second Module Enable/Disabled in Second Set, by background thread" Width="500" Grid.Row="7" Command="{Binding SwitchCommand4}" />
    </Grid>
</Window>

MainViewModel.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IOOC;
using ModKey;
using System.Windows.Data;
using System.Globalization;
using System.Threading;
using System.Collections.ObjectModel;
using System.Windows.Input;
using System.Threading.Tasks;
using System.Diagnostics;

namespace MultiBindTest
{
    class MainViewModel
    {
        public MainViewModel()
        {
            ModuleKey.setModules();
        }

        public ObservableCollection<Module> ModuleCollection1
        {
            get { return ModuleKey.module_objects1; }
        }

        public ItemsObservableObservableCollection<Module> ModuleCollection2
        {
            get { return ModuleKey.module_objects2; }
        }


        RelayCommand switchCommand1;
        public ICommand SwitchCommand1
        {
            get
            {
                if (switchCommand1 == null)
                {
                    switchCommand1 = new RelayCommand(SwitchExecute1, CanSwitchExecute1);
                }
                return switchCommand1;
            }
        }
        private void SwitchExecute1(object parameter)
        {
            Module item1 = ModuleKey.module_objects1.FirstOrDefault(i => i.ModuleID == 1);
            if (item1.ModuleEnabled)
                item1.ModuleEnabled = false;
            else
                item1.ModuleEnabled = true;
        }
        private bool CanSwitchExecute1(object parameter)
        {
            return true;
        }


        RelayCommand switchCommand2;
        public ICommand SwitchCommand2
        {
            get
            {
                if (switchCommand2 == null)
                {
                    switchCommand2 = new RelayCommand(SwitchExecute2, CanSwitchExecute2);
                }
                return switchCommand2;
            }
        }
        private void SwitchExecute2(object parameter)
        {
            Task.Factory.StartNew(() =>
            {
                Module item = ModuleKey.module_objects1.FirstOrDefault(i => i.ModuleID == 1);
                if (item.ModuleEnabled)
                    item.ModuleEnabled = false;
                else
                    item.ModuleEnabled = true;
            });
        }
        private bool CanSwitchExecute2(object parameter)
        {
            return true;
        }


        RelayCommand switchCommand3;
        public ICommand SwitchCommand3
        {
            get
            {
                if (switchCommand3 == null)
                {
                    switchCommand3 = new RelayCommand(SwitchExecute3, CanSwitchExecute3);
                }
                return switchCommand3;
            }
        }
        private void SwitchExecute3(object parameter)
        {
            Module item = ModuleKey.module_objects2.FirstOrDefault(i => i.ModuleID == 1);
            if (item.ModuleEnabled)
                item.ModuleEnabled = false;
            else
                item.ModuleEnabled = true;
        }
        private bool CanSwitchExecute3(object parameter)
        {
            return true;
        }


        RelayCommand switchCommand4;
        public ICommand SwitchCommand4
        {
            get
            {
                if (switchCommand4 == null)
                {
                    switchCommand4 = new RelayCommand(SwitchExecute4, CanSwitchExecute4);
                }
                return switchCommand4;
            }
        }
        private void SwitchExecute4(object parameter)
        {
            Task.Factory.StartNew(() =>
            {
                Module item = ModuleKey.module_objects2.FirstOrDefault(i => i.ModuleID == 1);
                if (item.ModuleEnabled)
                    item.ModuleEnabled = false;
                else
                    item.ModuleEnabled = true;
            });
        }
        private bool CanSwitchExecute4(object parameter)
        {
            return true;
        }
    }

    public class IsEnabledMultiValueConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            try
            {
                int ModuleID = (int)values[0];
                bool ModuleEnabled = (bool)values[1];
                string ModuleLicenseDate = (string)values[2];

                DateTimeFormatInfo dtfi = new DateTimeFormatInfo();
                dtfi.ShortDatePattern = "yyyy-MM-dd";
                dtfi.DateSeparator = "-";
                DateTime MLicenseDate = System.Convert.ToDateTime(ModuleLicenseDate, dtfi);

                return (ModuleEnabled && (MLicenseDate >= DateTime.Now));
            }
            catch
            {
                return false;
            }
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    public class RelayCommand : ICommand
    {
        #region Fields

        readonly Action<object> _execute;
        readonly Predicate<object> _canExecute;

        #endregion // Fields

        #region Constructors

        /// <summary>
        /// Creates a new command that can always execute.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        public RelayCommand(Action<object> execute)
            : this(execute, null)
        {
        }

        /// <summary>
        /// Creates a new command.
        /// </summary>
        /// <param name="execute">The execution logic.</param>
        /// <param name="canExecute">The execution status logic.</param>
        public RelayCommand(Action<object> execute, Predicate<object> canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");

            _execute = execute;
            _canExecute = canExecute;
        }

        #endregion // Constructors

        #region ICommand Members

        [DebuggerStepThrough]
        public bool CanExecute(object parameter)
        {
            return _canExecute == null ? true : _canExecute(parameter);
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public void Execute(object parameter)
        {
            _execute(parameter);
        }

        #endregion // ICommand Members
    }
}

IOOC.cs

using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Collections;

namespace IOOC
{
    /// <summary>
    ///     This class adds the ability to refresh the list when any property of
    ///     the objects changes in the list which implements the INotifyPropertyChanged. 
    ///
    /// </summary>
    /// <typeparam name="T">
    ///     The type of elements in the collection.
    /// </typeparam>
    public class ItemsObservableObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
    {
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                RegisterPropertyChanged(e.NewItems);
            }
            else if (e.Action == NotifyCollectionChangedAction.Remove)
            {
                UnRegisterPropertyChanged(e.OldItems);
            }
            else if (e.Action == NotifyCollectionChangedAction.Replace)
            {
                UnRegisterPropertyChanged(e.OldItems);
                RegisterPropertyChanged(e.NewItems);
            }

            base.OnCollectionChanged(e);
        }

        protected override void ClearItems()
        {
            UnRegisterPropertyChanged(this);
            base.ClearItems();
        }

        private void RegisterPropertyChanged(IList items)
        {
            foreach (INotifyPropertyChanged item in items)
            {
                item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
            }
        }

        private void UnRegisterPropertyChanged(IList items)
        {
            foreach (INotifyPropertyChanged item in items)
            {
                item.PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
            }
        }

        private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
    }
}

ModKey.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IOOC;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace ModKey
{
    public static class ModuleKey
    {
        public static void setModules()
        {
            module_objects1 = new ObservableCollection<Module>();
            module_objects1.Add(new Module(0, 0, "Customer Services", "CSM", "#FFA32A3D", true, "2014-06-30"));
            module_objects1.Add(new Module(1, 1, "Asset Management", "AMS", "#FF0C51DB", true, "2014-06-30"));
            module_objects1.Add(new Module(2, 2, "Works Management", "WKS", "#FF8BDB46", false, "2014-06-30"));
            module_objects1.Add(new Module(3, 3, "Project Management", "PRJ", "#FFC7BA00", false, "2014-06-30"));

            module_objects2 = new ItemsObservableObservableCollection<Module>();
            module_objects2.Add(new Module(0, 0, "Customer Services", "CSM", "#FFA32A3D", true, "2014-06-30"));
            module_objects2.Add(new Module(1, 1, "Asset Management", "AMS", "#FF0C51DB", true, "2014-06-30"));
            module_objects2.Add(new Module(2, 2, "Works Management", "WKS", "#FF8BDB46", false, "2014-06-30"));
            module_objects2.Add(new Module(3, 3, "Project Management", "PRJ", "#FFC7BA00", false, "2014-06-30"));
        }

        public static ObservableCollection<Module> module_objects1
        {
            get;
            set;
        }

        public static ItemsObservableObservableCollection<Module> module_objects2
        {
            get;
            set;
        }
    }

    public class Module : INotifyPropertyChanged
    {
        public Module(int ModuleID, int ModuleIndex, string ModuleName, string ModuleAbbreviation, string ModuleColor, bool ModuleEnabled, string ModuleLicenseDate)
        {
            this.ModuleID = ModuleID;
            this.ModuleIndex = ModuleIndex;
            this.ModuleName = ModuleName;
            this.ModuleAbbreviation = ModuleAbbreviation;
            this.ModuleColor = ModuleColor;

            this.ModuleEnabled = ModuleEnabled;
            this.ModuleLicenseDate = ModuleLicenseDate;
        }

        private bool _module_enabled;

        public int ModuleID { get; private set; }
        public int ModuleIndex { get; private set; }
        public string ModuleName { get; private set; }
        public string ModuleAbbreviation { get; private set; }
        public string ModuleColor { get; private set; }
        public bool ModuleEnabled
        {
            get { return _module_enabled; }
            set
            {
                _module_enabled = value;
                RaisePropertyChanged("ModuleEnabled");
            }
        }
        public string ModuleLicenseDate { get; private set; }


        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
        }
    }
}

該應用程序如下所示: 在此輸入圖像描述 每個系列都有兩個按鈕。
第一個在主線程上運行一個項屬性更新,這在兩個集合上都可以正常工作。
第二個在后台線程上運行項屬性更新,這在第一個集合上運行正常。 第二個引發異常。 在此輸入圖像描述

[EDIT1]調用堆棧

    [External Code] 
>   MultiBindTest.exe!IOOC.ItemsObservableObservableCollection<ModKey.Module>.item_PropertyChanged(object sender = {ModKey.Module}, System.ComponentModel.PropertyChangedEventArgs e = {System.ComponentModel.PropertyChangedEventArgs}) Line 61 + 0x25 bytes   C#
    MultiBindTest.exe!ModKey.Module.RaisePropertyChanged(string propertyName = "ModuleEnabled") Line 79 + 0x32 bytes    C#
    MultiBindTest.exe!ModKey.Module.ModuleEnabled.set(bool value = false) Line 68 + 0xe bytes   C#
    MultiBindTest.exe!MultiBindTest.MainViewModel.SwitchExecute4.AnonymousMethod__8() Line 134 + 0xc bytes  C#
    [External Code] 

[EDIT2]只是整理一下這里的痕跡

發生了System.NotSupportedException HResult = -2146233067
Message =此類型的CollectionView不支持從與Dispatcher線程不同的線程更改其SourceCollection。
Source = PresentationFramework StackTrace:System.Wolows.Data.CollectionView.OnCollectionChanged(Object sender,NotifyCollectionChangedEventArgs args)at System.Collections.ObjectModel.ObservableCollection 1.OnCollectionChanged(NotifyCollectionChangedEventArgs e) at IOOC.ItemsObservableObservableCollection 1.item_PropertyChanged(Object sender,PropertyChangedEventArgs e )在C:\\ Users \\ HFisher \\ Documents \\ Visual Studio 2010 \\ Projects \\ MultiBindTest \\ MultiBindTest \\ iooc.cs:第63行
的InnerException:

將您的代碼更改為此代碼,它將使用UI線程進行更新

    private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        Application.Current.Dispatcher.Invoke(() =>base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)));
    }

如果.net 4.0你可以這樣做:

    Application.Current.Dispatcher.Invoke(new Action(() =>base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))));

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM