【问题标题】:Best/cleanest strategy to update an ObservableCollection from another thread从另一个线程更新 ObservableCollection 的最佳/最干净策略
【发布时间】:2017-06-30 06:35:47
【问题描述】:

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

在我的示例中,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);
                }
            }));
        }
    }
}

【问题讨论】:

  • I think my solution is obsolete and should not be used anymore. 为什么?你有问题吗?如果有,请列举它们。如果没有,就没有理由改变。我们不可能找到比已经满足您要求的解决方案更好的解决方案
  • 您可以编写它更干净,而无需复制更新集合的代码
  • 我能补充的就是让这个工作为你的测试工作,检查调度程序是否为空。 public class RootDispatcherFetcher {private static Dispatcher _rootDispatcher = null;public static Dispatcher RootDispatcher{ get { _rootDispatcher = _rootDispatcher ?? Application.Current != null ? Application.Current.Dispatcher : new Dispatcher(...); return _rootDispatcher;} // unit tests can get access to this via InternalsVisibleTo internal set { _rootDispatcher = value; } } }
  • herehere
  • 这个解决方案怎么样? jonathanantoine.com/2011/09/24/… 使用 BindingOperations.EnableCollectionSynchronization() ?

标签: c# wpf multithreading observablecollection dispatcher


【解决方案1】:

如果您使用的是 .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);
        }
    }
}

【讨论】:

    【解决方案2】:

    我无法判断您是在此处实现 MVC 还是 MVW 模式,但即使使用桌面应用程序也是一种很好的做法。

    因此,我认为 ObservableCollection 是视图的一部分,而不是模型,因为它通常不是正常运行的模型所必需的,而是用于 WPF UI 绑定的。 我这么说的主要原因是它是可变的,并且它与 WPF 交互的整个方式都依赖于可变性。

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

    相反,在您的模型中执行所有异步/多线程处理,因为这是大部分处理将发生的地方。

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

    【讨论】:

    • 但是我仍然需要一种Dispatcher 来更新我的UI?!
    • 如果您不从另一个线程更新您的 UI,则不会。对于进度报告或类似更新,请使用 IProgress 模式。是的,它在幕后做了很多事情,但你必须编写和维护的代码才是最重要的。 IProgress 是一个更干净的范例。只需确保您传回的任何进度对象都是松散耦合的 - 即它不会在代码的模型和视图部分之间建立牢固的关系。
    猜你喜欢
    • 2017-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多