2013-01-15 67 views
22

我正在構建一個應用程序,它使用了許多ItemControls(datagrids和listviews)。爲了方便地更新從後臺線程這些名單我使用這個擴展ObservableCollections,這工作得很好:升級到.NET 4.5:ItemsControl與其項目不一致源

http://geekswithblogs.net/NewThingsILearned/archive/2008/01/16/have-worker-thread-update-observablecollection-that-is-bound-to-a.aspx

今天我安裝VS12(這反過來又安裝了.NET 4.5),因爲我想用一個爲.NET 4.5編寫的組件。甚至在我的項目升級到.NET 4.5(從4.0)之前,我的數據網格從workerthread更新時開始拋出InvalidOperationException。異常消息:

此異常被拋出,因爲發電機控制「System.Windows.Controls.DataGrid Items.Count:5」名「(未命名)」已收到不同意CollectionChanged事件序列Items集合的當前狀態。檢測到如下區別: 累計計數圖4是從實際計數5.不同[累計計數(計數在上次復位+ #Adds - 自上次復位#Removes)。]

攝製代碼:

XAML:

<Window x:Class="Test1.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="MainWindow" Height="350" Width="525"> 
    <Grid> 
     <DataGrid ItemsSource="{Binding Items, Mode=OneTime}" PresentationTraceSources.TraceLevel="High"/>  
    </Grid> 
</Window> 

代碼:

public partial class MainWindow : Window 
{ 
    public ExtendedObservableCollection<int> Items { get; private set; } 

    public MainWindow() 
    { 
     InitializeComponent(); 
     Items = new ExtendedObservableCollection<int>(); 
     DataContext = this; 
     Loaded += MainWindow_Loaded; 
    } 

    void MainWindow_Loaded(object sender, RoutedEventArgs e) 
    { 
      Task.Factory.StartNew(() => 
      { 
       foreach (var item in Enumerable.Range(1, 500)) 
       { 
        Items.Add(item); 
       } 
      });     
    } 
} 
+0

我來自Microsoft .NET Framework團隊。您可以向我們發送在Microsoft dot com上的netfx45compat上重現問題的項目嗎?我想看看。此致,Varun Gupta – Varun

+1

您對這個問題有任何進展嗎?我沒有在我的Win8開發盒中看到它,但我有一個擁有Win7和.NET 4.5的用戶,並且無法使用我的軟件。我們正試圖卸載4.5並轉到4.0。 – Thomas

+1

已驗證:回滾修復了這個問題。 – Thomas

回答

35

WPF 4.5提供了一些新的FUNC訪問非UI線程上的集合。

它通過WPF可以訪問和修改線程 以外的數據集合,而不是創建集合的數據集合。這使您可以使用後臺線程 作爲數據庫從外部源(如 )接收數據,並在UI線程上顯示數據。通過使用另一個 線程修改集合,您的用戶界面仍然是 響應用戶交互。

這可以通過在BindingOperations類上使用靜態方法EnableCollectionSynchronization來完成。

如果你有大量的數據收集或修改,您可能需要使用 後臺線程來收集和修改數據,使用戶界面 將保持反應的輸入。要啓用多個線程到 訪問集合,請調用EnableCollectionSynchronization方法。 當您調用 EnableCollectionSynchronization(IEnumerable,Object)方法的超載時, 系統在您訪問集合時會鎖定該集合。要指定回調 來自己鎖定集合,請調用 EnableCollectionSynchronization(IEnumerable,Object, CollectionSynchronizationCallback)重載。

用法如下。創建一個用作集合同步的鎖的對象。然後調用BindingsOperations的EnableCollectionSynchronization方法,並傳遞要同步的集合和用於鎖定的對象。

我已更新您的代碼並添加了詳細信息。另外我將集合更改爲正常的ObservableCollection以避免衝突。

public partial class MainWindow : Window{ 
    public ObservableCollection<int> Items { get; private set; } 

    //lock object for synchronization; 
    private static object _syncLock = new object(); 

    public MainWindow() 
    { 
    InitializeComponent(); 
    Items = new ObservableCollection<int>(); 

    //Enable the cross acces to this collection elsewhere 
    BindingOperations.EnableCollectionSynchronization(Items, _syncLock); 

    DataContext = this; 
    Loaded += MainWindow_Loaded; 
    } 

    void MainWindow_Loaded(object sender, RoutedEventArgs e) 
    { 
     Task.Factory.StartNew(() => 
     { 
      foreach (var item in Enumerable.Range(1, 500)) 
      { 
       lock(_syncLock) { 
        Items.Add(item); 
       } 
      } 
     });     
    } 
} 

參見:http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux

+0

你也應該在你的後臺線程中使用鎖對象 - > 'lock(_syncLock){Items.Add(item)}' – DELUXEnized

+0

@DELUXEnized不需要那個原因bindingoperations會自動爲我做這個 – Jehof

+0

儘可能我瞭解這種方法,它只是告訴綁定系統在訪問集合時使用哪個鎖。您仍然必須確保在從後臺線程使用集合時鎖定集合。該集合應該如何知道它何時從後臺線程訪問? – DELUXEnized

6

從Jehof的答案是正確的。

我們目前還不能定位4.5,並且已經允許後臺更新的自定義可觀察集合(通過在事件通知期間使用Dispatcher)發生此問題。

如果有人發現它是有用的,我已經在使用我們的應用程序下面的代碼面向.NET 4.0,使其能夠使用這個功能,如果執行環境是.NET 4.5:

public static void EnableCollectionSynchronization(IEnumerable collection, object lockObject) 
{ 
    // Equivalent to .NET 4.5: 
    // BindingOperations.EnableCollectionSynchronization(collection, lockObject); 
    MethodInfo method = typeof(BindingOperations).GetMethod("EnableCollectionSynchronization", new Type[] { typeof(IEnumerable), typeof(object) }); 
    if (method != null) 
    { 
     method.Invoke(null, new object[] { collection, lockObject }); 
    } 
} 
11

總結本主題,這AsyncObservableCollection與.NET 4和.NET 4.5 WPF應用程序一起使用。

using System; 
using System.Collections; 
using System.Collections.ObjectModel; 
using System.Collections.Specialized; 
using System.Linq; 
using System.Windows.Data; 
using System.Windows.Threading; 

namespace WpfAsyncCollection 
{ 
    public class AsyncObservableCollection<T> : ObservableCollection<T> 
    { 
     public override event NotifyCollectionChangedEventHandler CollectionChanged; 
     private static object _syncLock = new object(); 

     public AsyncObservableCollection() 
     { 
      enableCollectionSynchronization(this, _syncLock); 
     } 

     protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) 
     { 
      using (BlockReentrancy()) 
      { 
       var eh = CollectionChanged; 
       if (eh == null) return; 

       var dispatcher = (from NotifyCollectionChangedEventHandler nh in eh.GetInvocationList() 
            let dpo = nh.Target as DispatcherObject 
            where dpo != null 
            select dpo.Dispatcher).FirstOrDefault(); 

       if (dispatcher != null && dispatcher.CheckAccess() == false) 
       { 
        dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => OnCollectionChanged(e))); 
       } 
       else 
       { 
        foreach (NotifyCollectionChangedEventHandler nh in eh.GetInvocationList()) 
         nh.Invoke(this, e); 
       } 
      } 
     } 

     private static void enableCollectionSynchronization(IEnumerable collection, object lockObject) 
     { 
      var method = typeof(BindingOperations).GetMethod("EnableCollectionSynchronization", 
            new Type[] { typeof(IEnumerable), typeof(object) }); 
      if (method != null) 
      { 
       // It's .NET 4.5 
       method.Invoke(null, new object[] { collection, lockObject }); 
      } 
     } 
    } 
} 
+0

我應該這樣做來覆寫OnPropertyChanged() – Felix

+0

Works 4.6% 。 –

0

這是使用的發行版VS 2017年,可能有這個問題的Windows版本10 1607用戶。

Microsoft Visual Studio Community 2017 
Version 15.1 (26403.3) Release 
VisualStudio.15.Release/15.1.0+26403.3 
Microsoft .NET Framework 
Version 4.6.01586 

你並不需要也不EnableCollectionSynchronization

<ListBox x:Name="FontFamilyListBox" SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}" Width="{Binding FontFamilyWidth, Mode=TwoWay}" 
     SelectedItem="{Binding FontFamilyItem, Mode=TwoWay}" 
     ItemsSource="{Binding FontFamilyItems}" 
      diag:PresentationTraceSources.TraceLevel="High"> 
    <ListBox.ItemTemplate> 
     <DataTemplate DataType="typeData:FontFamilyItem"> 
      <Grid> 
       <TextBlock Text="{Binding}" diag:PresentationTraceSources.TraceLevel="High"/> 

      </Grid> 
     </DataTemplate> 
    </ListBox.ItemTemplate> 
</ListBox> 

public ObservableCollection<string> fontFamilyItems; 
public ObservableCollection<string> FontFamilyItems 
{ 
    get { return fontFamilyItems; } 
    set { SetProperty(ref fontFamilyItems, value, nameof(FontFamilyItems)); } 
} 

public string fontFamilyItem; 
public string FontFamilyItem 
{ 
    get { return fontFamilyItem; } 
    set { SetProperty(ref fontFamilyItem, value, nameof(FontFamilyItem)); } 
} 

private List<string> GetItems() 
{ 
    List<string> fonts = new List<string>(); 
    foreach (System.Windows.Media.FontFamily font in Fonts.SystemFontFamilies) 
    { 
     fonts.Add(font.Source); 
     .... 
     other stuff.. 
    } 
    return fonts; 
} 

public async void OnFontFamilyViewLoaded(object sender, EventArgs e) 
{ 
    DisposableFontFamilyViewLoaded.Dispose(); 
    Task<List<string>> getItemsTask = Task.Factory.StartNew(GetItems); 

    try 
    { 
     foreach (string item in await getItemsTask) 
     { 
      FontFamilyItems.Add(item); 
     } 
    } 
    catch (Exception x) 
    { 
     throw new Exception("Error - " + x.Message); 
    } 

    ... 
    other stuff 
} 
+0

這不是一個新問題,而是回答所問的問題,但對於可能遇到此問題的Window 10用戶。 – Nasheayahu

相關問題