从线程添加到 ObservableCollection<string>
Adding to ObservableCollection<string> from threads
我有一系列 ObservableCollection<string>
,每个都绑定到自己的 listbox
。我有四个工作线程,每个线程都需要能够添加到这些集合中。问题是,我无法从非 UI 线程添加到这些集合。
通常(如果我不使用数据绑定)我会使用类似的东西:
private delegate void ProgressBarStepConsumer(ProgressBar pBar);
public static void ProgressBarTakeStep(ProgressBar pBar)
{
if (pBar.InvokeRequired)
{
pBar.Invoke(new ProgressBarStepConsumer(ProgressBarTakeStep), pBar);
}
else
{
lock (pBar)
{
pBar.PerformStep();
}
}
}
但是,由于我没有直接访问该控件,我不知道该怎么做,所以任何建议都会有所帮助。
我之前的(错误尝试)是这样的:
switch (Path.GetFileNameWithoutExtension(file).Substring(Path.GetFileNameWithoutExtension(file).Length - 2, 2))
{
case "UL":
{
lock (_ulFileList)
{
_ulFileList.Add(Path.GetFileName(file));
}
break;
}
case "UR":
{
lock (_urFileList)
{
_urFileList.Add(Path.GetFileName(file));
}
break;
}
case "LR":
{
lock (_lrFileList)
{
_lrFileList.Add(Path.GetFileName(file));
}
break;
}
case "LL":
{
lock (_llFileList)
{
_llFileList.Add(Path.GetFileName(file));
}
break;
}
}
我的 Thread
结构是这样的:
Thread ulThread = new Thread(() => ConverterWorker(ulQueue, Corner.UL, destPath));
Thread urThread = new Thread(() => ConverterWorker(urQueue, Corner.UR, destPath));
Thread lrThread = new Thread(() => ConverterWorker(lrQueue, Corner.LR, destPath));
Thread llThread = new Thread(() => ConverterWorker(llQueue, Corner.LL, destPath));
其中 ConverterWorker()
是一个 private void
方法。
在事先研究我的问题时,我意识到每次迭代都进行跨线程操作(switch
在循环内)可能效率很低,所以我将更改存储在本地然后更新UI 批量更大,但一旦我可以访问这些集合,我就可以自己解决这个问题。
自从问了这个问题后,我发现可以 Dispatch
一个任务由 UI 线程执行。
这样做是这样的:
Application.Current.Dispatcher.Invoke(() =>
{
_ulFileList.Add(Path.GetFileName(file));
});
您面临的问题是线程同步是一个UI问题,而您正在做的工作是业务逻辑的一部分。在设计良好的系统中,业务逻辑不了解 UI,因此它无法了解同步细节。摆脱 "the business logic must synchronize through the UI logic, but it should have no knowledge of the UI" 情况的解决方案是依赖倒置。
依赖倒置是一种常见的设计实践。事实上,它是如此常见以至于它是 SOLID principles 中的 D。在您的情况下,这意味着业务逻辑对 UI 同步逻辑的依赖性是相反的,因此 UI 逻辑依赖于业务逻辑,这是正确的设计方法,因为 UI层在业务层之上。
建议将业务关注点放入与 UI 关注点不同的程序集中。 UI 程序集引用业务程序集,而业务程序集又不应引用与特定 UI 技术相关的任何程序集(例如任何 System.Web
或 System.Windows
程序集) .但是,这会使您无法访问 Application.Current.Dispatcher.Invoke
。这实际上是一件好事,因为这是UI层的一部分,业务逻辑应该不知道。有两种可能的方法可以解决您现在面临的同步困境:
UI独立抽象
在 System.ComponentModel
命名空间中,您会找到 ISynchronizeInvoke
接口,它用于需要同步调用的情况。尽管此接口在 Windows Forms 中广泛使用,但它实际上是 UI 不特定的(并且未在任何 UI 特定程序集中定义)。所以你可以放心地在你的业务中引入对这个接口的依赖class。假设 ConverterWorker
方法定义在 MyBusinessClass
class 中,您可以在构造函数中传递接口的实现:
public class MyBusinessClass {
private ISynchronizeInvoke syncInv;
public MyBusinessClass(ISynchronizeInvoke syncInv) {
this.syncInv = syncInv;
}
}
您现在可以执行所有需要通过 syncInv
同步的操作,并独立于特定的 UI 技术进行同步。在 Windows 表单中,所有控件(包括表单)都是 ISynchronizeInvoke
实现,您可以在同步业务对象时简单地传递表单。在 WPF 中,你必须自己实现接口,这是微不足道的,因为你只需要在 Invoke
方法中调用 Application.Current.Dispatcher.Invoke
(并分别对 BeginInvoke
等执行相同的操作)。
依赖注入
使用依赖注入,您不会将抽象直接传递给您的业务 class,而是将更高层代码(在您的情况下为 UI)"injects" 依赖代码传递给下层,其 classes 可以拾取和使用。现有的依赖注入框架很多,但是您可以自己实现一种简单形式的依赖注入,这在简单的场景中很适合您。在您的业务逻辑(或两层使用的独立框架)中,您可以这样定义:
public static class DependencyManager {
private static Dictionary<Type, object> dependencies = new Dictionary<Type, object>();
public static void AddDependency<TInterf, TImpl>(TImpl dependency)
where TImpl : TInterf {
dependencies[typeof(TInterf)] = dependency;
}
public static T GetDependency<T>() {
T dependency;
bool hasDependency = dependencies.TryGetValue(typeof(T), out dependency);
if (hasDependency) {
return dependency;
}
else {
return default(T)
}
}
}
业务层可以这样获取ISynchronizeInvoke
:
ISynchronizeInvoke syncInv = DependencyManager.GetDependency<ISynchronizeInvoke>();
UI层可以像这样注入依赖(这必须在使用依赖依赖的业务对象之前发生):
DependencyManager.AddDependency<ISynchronizeInvoke, MySyncInv>(mySyncInvImplementation);
我有一系列 ObservableCollection<string>
,每个都绑定到自己的 listbox
。我有四个工作线程,每个线程都需要能够添加到这些集合中。问题是,我无法从非 UI 线程添加到这些集合。
通常(如果我不使用数据绑定)我会使用类似的东西:
private delegate void ProgressBarStepConsumer(ProgressBar pBar);
public static void ProgressBarTakeStep(ProgressBar pBar)
{
if (pBar.InvokeRequired)
{
pBar.Invoke(new ProgressBarStepConsumer(ProgressBarTakeStep), pBar);
}
else
{
lock (pBar)
{
pBar.PerformStep();
}
}
}
但是,由于我没有直接访问该控件,我不知道该怎么做,所以任何建议都会有所帮助。
我之前的(错误尝试)是这样的:
switch (Path.GetFileNameWithoutExtension(file).Substring(Path.GetFileNameWithoutExtension(file).Length - 2, 2))
{
case "UL":
{
lock (_ulFileList)
{
_ulFileList.Add(Path.GetFileName(file));
}
break;
}
case "UR":
{
lock (_urFileList)
{
_urFileList.Add(Path.GetFileName(file));
}
break;
}
case "LR":
{
lock (_lrFileList)
{
_lrFileList.Add(Path.GetFileName(file));
}
break;
}
case "LL":
{
lock (_llFileList)
{
_llFileList.Add(Path.GetFileName(file));
}
break;
}
}
我的 Thread
结构是这样的:
Thread ulThread = new Thread(() => ConverterWorker(ulQueue, Corner.UL, destPath));
Thread urThread = new Thread(() => ConverterWorker(urQueue, Corner.UR, destPath));
Thread lrThread = new Thread(() => ConverterWorker(lrQueue, Corner.LR, destPath));
Thread llThread = new Thread(() => ConverterWorker(llQueue, Corner.LL, destPath));
其中 ConverterWorker()
是一个 private void
方法。
在事先研究我的问题时,我意识到每次迭代都进行跨线程操作(switch
在循环内)可能效率很低,所以我将更改存储在本地然后更新UI 批量更大,但一旦我可以访问这些集合,我就可以自己解决这个问题。
自从问了这个问题后,我发现可以 Dispatch
一个任务由 UI 线程执行。
这样做是这样的:
Application.Current.Dispatcher.Invoke(() =>
{
_ulFileList.Add(Path.GetFileName(file));
});
您面临的问题是线程同步是一个UI问题,而您正在做的工作是业务逻辑的一部分。在设计良好的系统中,业务逻辑不了解 UI,因此它无法了解同步细节。摆脱 "the business logic must synchronize through the UI logic, but it should have no knowledge of the UI" 情况的解决方案是依赖倒置。
依赖倒置是一种常见的设计实践。事实上,它是如此常见以至于它是 SOLID principles 中的 D。在您的情况下,这意味着业务逻辑对 UI 同步逻辑的依赖性是相反的,因此 UI 逻辑依赖于业务逻辑,这是正确的设计方法,因为 UI层在业务层之上。
建议将业务关注点放入与 UI 关注点不同的程序集中。 UI 程序集引用业务程序集,而业务程序集又不应引用与特定 UI 技术相关的任何程序集(例如任何 System.Web
或 System.Windows
程序集) .但是,这会使您无法访问 Application.Current.Dispatcher.Invoke
。这实际上是一件好事,因为这是UI层的一部分,业务逻辑应该不知道。有两种可能的方法可以解决您现在面临的同步困境:
UI独立抽象
在 System.ComponentModel
命名空间中,您会找到 ISynchronizeInvoke
接口,它用于需要同步调用的情况。尽管此接口在 Windows Forms 中广泛使用,但它实际上是 UI 不特定的(并且未在任何 UI 特定程序集中定义)。所以你可以放心地在你的业务中引入对这个接口的依赖class。假设 ConverterWorker
方法定义在 MyBusinessClass
class 中,您可以在构造函数中传递接口的实现:
public class MyBusinessClass {
private ISynchronizeInvoke syncInv;
public MyBusinessClass(ISynchronizeInvoke syncInv) {
this.syncInv = syncInv;
}
}
您现在可以执行所有需要通过 syncInv
同步的操作,并独立于特定的 UI 技术进行同步。在 Windows 表单中,所有控件(包括表单)都是 ISynchronizeInvoke
实现,您可以在同步业务对象时简单地传递表单。在 WPF 中,你必须自己实现接口,这是微不足道的,因为你只需要在 Invoke
方法中调用 Application.Current.Dispatcher.Invoke
(并分别对 BeginInvoke
等执行相同的操作)。
依赖注入
使用依赖注入,您不会将抽象直接传递给您的业务 class,而是将更高层代码(在您的情况下为 UI)"injects" 依赖代码传递给下层,其 classes 可以拾取和使用。现有的依赖注入框架很多,但是您可以自己实现一种简单形式的依赖注入,这在简单的场景中很适合您。在您的业务逻辑(或两层使用的独立框架)中,您可以这样定义:
public static class DependencyManager {
private static Dictionary<Type, object> dependencies = new Dictionary<Type, object>();
public static void AddDependency<TInterf, TImpl>(TImpl dependency)
where TImpl : TInterf {
dependencies[typeof(TInterf)] = dependency;
}
public static T GetDependency<T>() {
T dependency;
bool hasDependency = dependencies.TryGetValue(typeof(T), out dependency);
if (hasDependency) {
return dependency;
}
else {
return default(T)
}
}
}
业务层可以这样获取ISynchronizeInvoke
:
ISynchronizeInvoke syncInv = DependencyManager.GetDependency<ISynchronizeInvoke>();
UI层可以像这样注入依赖(这必须在使用依赖依赖的业务对象之前发生):
DependencyManager.AddDependency<ISynchronizeInvoke, MySyncInv>(mySyncInvImplementation);