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);
}
}
}
通常我会检查我是否可以访问 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);
}
}
}