Best/cleanest 从另一个线程更新 ObservableCollection 的策略

Best/cleanest strategy to update an ObservableCollection from another thread

通常我会检查我是否可以访问 ObservableCollection,如果没有,我会调用 Dispatcher。 Here (on Whosebug) 还有其他一些解决方案,但我不知道什么是最好和最干净的方法。我认为我的解决方案已经过时,不应再使用。

在我的示例中,ItemsCollection 绑定到 UI_UpdateTheCollectionFromAnotherThread() 将被调用(从另一个 thread 或将打开另一个 thread)并将数据临时保存到 items。之后我会检查访问权限并在需要时调用 dispatcher

这是我必须走的路还是有更好更清洁的解决方案?

FooClass

public class FooClass
{
    // ##############################################################################################################################
    // Properties
    // ##############################################################################################################################

    /// <summary>
    /// This Items are bound to my UI.
    /// </summary>
    public ObservableCollection<string> ItemsCollection { get; } = new ObservableCollection<string>();

    // ##############################################################################################################################
    // Singleton pattern
    // ##############################################################################################################################

    /// <summary>
    /// The instance of <see cref="FooClass"/>.
    /// </summary>
    internal static FooClass Instance => _Instance ?? (_Instance = new FooClass());
    private static FooClass _Instance;

    // ##############################################################################################################################
    // Konstruktor
    // ##############################################################################################################################

    private FooClass()
    {

    }


    // ##############################################################################################################################
    // Method
    // ##############################################################################################################################

    private void _UpdateTheCollectionFromAnotherThread()
    {
        List<string> items = new List<string>();

        //Here would be some logic to create and fill the items list....


        //and now apply them to the public list

        if (System.Windows.Application.Current.Dispatcher.CheckAccess())
        {
            ItemsCollection.Clear();
            foreach (string item in items)
            {
                ItemsCollection.Add(item);
            }
        }
        else
        {
            System.Windows.Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
            {
                ItemsCollection.Clear();
                foreach (string item in items)
                {
                    ItemsCollection.Add(item);
                }
            }));
        }
    }
}

我无法确定您在这里实现的是 MVC 模式还是 MVW 模式,但即使使用桌面应用程序,这样做也是一种很好的做法。

因此,我认为 ObservableCollection 是视图的一部分,而不是模型的一部分,因为功能模型通常不需要它,但 WPF UI 绑定需要它。 我这样说的主要原因是它是可变的,而且它与 WPF 交互的整个方式都依赖于可变性。

我不会有 _UpdateTheCollectionFromAnotherThread,因为您应该避免从另一个线程更新 UI。 这样做通常意味着在您的设计中混合了模型-视图-控制器的角色。

相反,在您的模型中执行所有 async/multithreading 操作,因为大量处理将在此处进行。

然后在 UI 线程中用模型更新 UI 会更简单。

如果您使用的是 .NET Framework 4.5 或更高版本,则可以通过调用 BindingOperations.EnableCollectionSynchronization 方法启用跨多个线程访问集合。注意这个方法必须在UI线程上调用:

public class FooClass
{
    private readonly object _lock = new object();
    public ObservableCollection<string> ItemsCollection { get; } = new ObservableCollection<string>();

    internal static FooClass Instance => _Instance ?? (_Instance = new FooClass());
    private static FooClass _Instance;

    private FooClass()
    {
        BindingOperations.EnableCollectionSynchronization(ItemsCollection, _lock);
    }

    private void _UpdateTheCollectionFromAnotherThread()
    {
        List<string> items = new List<string>();
        ItemsCollection.Clear();
        foreach (string item in items)
        {
            ItemsCollection.Add(item);
        }
    }
}

如果 FooClass 可能是在后台线程上创建的,您仍然可以通过不重复自己来改进您的代码:

public class FooClass
{
    public ObservableCollection<string> ItemsCollection { get; } = new ObservableCollection<string>();

    internal static FooClass Instance => _Instance ?? (_Instance = new FooClass());
    private static FooClass _Instance;

    private FooClass() { }

    private void _UpdateTheCollectionFromAnotherThread()
    {
        List<string> items = new List<string>();

        if (System.Windows.Application.Current.Dispatcher.CheckAccess())
            ClearCollection(items);
        else
            System.Windows.Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => ClearCollection(items)));
    }

    private void ClearCollection(List<string> items)
    {
        ItemsCollection.Clear();
        foreach (string item in items)
        {
            ItemsCollection.Add(item);
        }
    }
}