【问题标题】:Modifiing ItemsSource within BackgroundWorker Causes an Exception在 BackgroundWorker 中修改 ItemsSource 导致异常
【发布时间】:2014-12-30 23:49:36
【问题描述】:

我的应用程序中有一个带有 ObservableCollection 作为 ItemsSource 的 ListBox。 我也有几个类为这个 ItemsSource 提供数据。

    public ObservableCollection<Notification> NotificationItems { get; set; }
    private object _stocksLock = new object();

我像这样在构造函数中创建集合

this.NotificationItems = new ObservableCollection<Notification>();
System.Windows.Data.BindingOperations.EnableCollectionSynchronization(
       this.NotificationItems, _stocksLock);

我正在从多个 dll 程序集中加载为 ListBox 提供数据的模块。在 BackgroundWorker 中调用获取集合的 Notification 数据的方法

BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += GetPluginModuleNotifications;
List<IModul> Modules = new List<IModul>();
//[...]
foreach (IModul PluginModul in AssemblyList)
{
     //[...]
     Modules.Add(PluginModul);
     //[...]
}
this.Notifications.ItemsSource = this.NotificationItems;

object[] Parameter = new object[] { Modules, this.ComponentFactory, 
      this.MyListBox};

//-->Edited
worker.WorkerReportsProgress = true;
worker.ProgressChanged += OnProgressChanged;
//<--Edited

worker.RunWorkerAsync(Parameter);
//...

//-->EDITED
private void OnProgressChanged(object sender, ProgressChangedEventArgs e)
{
    Notification toAdd = (Notification)e.UserState;
    this.NotificationItems.Add(toAdd);
}
//<--EDITED

我希望每个 IModul 项目都为 ListBox 提供项目。 这部分工作正常,所以我想接收的数据被加载。 这是我的 BackgroundWorker.DoWork 事件

    private void GetPluginModuleNotifications(object sender, DoWorkEventArgs e)
    {
        object[] args = e.Argument as object[];
        if (args == null || args.Length != 3) return;
        List<IModul> Module = args[0] as List<IModul>;
        IComponentFactory Factory = args[1] as IComponentFactory;
        // DXListBox lb = args[2] as DXListBox;
        if (Module == null || Factory == null) return;

        foreach (IModul Modul in Module)
        {
            Notification[] res = Modul.GetImportantNotifications(Factory);
            if (res == null || res.Length == 0) continue;

            foreach (Notification notif in res)
            {
                //-->EDITED
                (sender as BackgroundWorker).ReportProgress(1, notif);
                System.Threading.Thread.Sleep(100);
                //this.ReceiveNotification(notif);
                //<--EDITED
            }
        }

    }

    private void ReceiveNotification(Notification obj)
    {
        if (obj == null) return;

        Dispatcher.BeginInvoke(new Action(() =>
        {
            this.NotificationItems.Add(obj);
            if (this.NotificationPanel.Width.Value == 0)
            this.NotificationPanel.Width = new GridLength(NOTIFICATION_BAR_WIDTH);
         }));
    }

NotificationPanel 的 XAML 如下所示:

 .<dx:DXListBox x:Name="Notifications" VerticalAlignment="Stretch" BorderBrush="Transparent" MouseDoubleClick="NotificationGotoSource" ItemsSource="{Binding NotificationItems}">
    <dx:DXListBox.ItemTemplate>
    <DataTemplate DataType="{x:Type cbcore:Notification}">
        <StackPanel Orientation="Horizontal">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="38" />
                    <ColumnDefinition Width="*" MinWidth="157"/>
                    <ColumnDefinition Width="15" />
                </Grid.ColumnDefinitions>
                <Image Source="{Binding ImageSource}" Grid.Column="0" Width="32" Height="32" VerticalAlignment="Top" HorizontalAlignment="Left" />
                <StackPanel Orientation="Vertical" Grid.Column="1">
                    <Label FontWeight="Bold" FontSize="10" MaxHeight="25" MaxWidth="150">
                        <TextBlock Text="{Binding Headline}" TextWrapping="Wrap" />
                    </Label>

                    <Label FontWeight="Normal" FontSize="9" MaxHeight="100" MaxWidth="150">
                        <TextBlock Text="{Binding Note}" TextWrapping="Wrap" />
                    </Label>
                </StackPanel>
                <Label Cursor="Hand" Padding="0" Margin="0" MouseLeftButtonUp="Notification_RemoveSelected" Grid.Column="2" 
                        OverridesDefaultStyle="True" BorderBrush="Black" Background="Transparent" 
                        FontSize="8" FontWeight="Bold" VerticalAlignment="Top" HorizontalAlignment="Right">
                    <TextBlock Foreground="Red" TextAlignment="Center">X</TextBlock>
                </Label>
            </Grid>
        </StackPanel>
    </DataTemplate>
    </dx:DXListBox.ItemTemplate>
</dx:DXListBox>

当我运行我的应用程序时,它会导致 XamlParseException 对象由另一个线程拥有并且主 ui 线程无法访问它。

谁能帮我解决这个问题?

【问题讨论】:

    标签: c# wpf devexpress backgroundworker xamlparseexception


    【解决方案1】:

    BackgroundWorker.WorkerSupportProgress 设置为True 并附加ProgressChangedEvent。将您的 ReceiveNotification 方法替换为 ReportProgress 调用以更新 UI。 ProgressChangedEventHandler 正在编组到 UI 线程,因此不需要调用。

    【讨论】:

    • 我之前和现在也尝试过这种方式,但还是出现了异常。
    • 我已经添加了 sn-ps。我想这就是我所做的全部修改。用 //-->EDITED 在代码块中标记
    【解决方案2】:

    我认为您使用了错误的调度程序。在您的情况下,您使用后台工作人员的调度程序。

    尽量保持UI Dispatcher的引用,根据here你只需要用Application.Current.Dispatcher访问即可。

    【讨论】:

    • 使用 Application.Current.Dispatcher 也会导致该异常
    • 我尝试制作一个小项目(没有 devExpress)并且解决方案 og Sievajet 为我工作。也许您的例外不是来自您的后台工作人员....对不起,我不知道如何提供帮助
    【解决方案3】:

    抛出异常不是因为 ObservableCollection 属于另一个线程,而是 Notification 模型中的公共 BitmapImage ImageSource。

    当应用程序尝试使用 Binding 读取 BitmapImage 时,它​​会失败。所以我的解决方案如下所示:

        private BitmapImage _ImageSource = null;
        public object _originalImageSource= null;
        public object ImageSource
        {
            get { return this._ImageSource; }
            set
            {
                this._originalImageSource = value;
                if (this._ImageSource != value)
                {
                    this._ImageSource = value is BitmapImage ? (BitmapImage)value : 
                        value is Uri ?
                        new BitmapImage(value as Uri) :
                        new BitmapImage(new Uri(value.ToString()));
                    this.RaisePropertyChanged("ImageSource");
                }
            }
        }
    

    在 OnProgressChanged 方法中,我使用 _originalImageSource 创建通知的 1:1 副本以创建新的 BitmapImage

        //-->EDITED
        private void OnProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            Notification t = (Notification)e.UserState;
            Notification toAdd = new Notification(t.Parent, t.OriginalSource, t.Headline, t.Note, t._originalImageSource);
            this.NotificationItems.Add(toAdd);
    
            if (this.NotificationItems.Count > 0 && this.NotificationPanel.Width.Value == 0)
                this.NotificationPanel.Width = new GridLength(NOTIFICATION_BAR_WIDTH);
        }
        //<--EDITED
    

    非常感谢您的支持。

    【讨论】:

      猜你喜欢
      • 2013-01-27
      • 1970-01-01
      • 2012-03-21
      • 1970-01-01
      • 2012-02-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多