我有一系列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更大的批次,但是一旦我可以访问这些集合,我就可以自己解决这个问题。
答案 0 :(得分:3)
您面临的问题是线程同步是一个UI问题,而您正在进行的工作是业务逻辑的一部分。在设计良好的系统中,业务逻辑不了解UI,因此无法知道同步细节。摆脱业务逻辑的情况的解决方案必须通过UI逻辑同步,但它应该不知道UI&#34;是堕落的倒退。
依赖倒置是一种常见的设计实践。事实上它是如此常见,以至于SOLID principles中的 D 。在您的情况下,这意味着业务逻辑对UI同步逻辑的依赖性被反转,因此UI逻辑依赖于业务逻辑,这是正确的设计方法,因为UI层位于业务层之上。
建议将业务问题放在与UI关注点不同的程序集中。 UI程序集引用业务程序集,而该程序集又不应引用与特定UI技术相关的任何程序集(例如任何System.Web
或System.Windows
程序集)。但是,这会使Application.Current.Dispatcher.Invoke
无法访问。这实际上是一件好事,因为这是UI层的一部分,业务逻辑应该不知道。有两种方法可以解决您现在面临的同步困境:
UI独立抽象
在System.ComponentModel
命名空间中,您将找到ISynchronizeInvoke
接口,该接口用于需要同步呼叫的情况。尽管此接口在Windows窗体中广泛使用,但它实际上是UI非特定的(并且未在任何UI特定程序集中定义)。因此,您可以安全地在业务类中为此接口引入依赖项。假设在ConverterWorker
类中定义了MyBusinessClass
方法,您可以在构造函数中传递接口的实现:
public class MyBusinessClass {
private ISynchronizeInvoke syncInv;
public MyBusinessClass(ISynchronizeInvoke syncInv) {
this.syncInv = syncInv;
}
}
您现在可以执行需要通过syncInv
同步的所有操作,并独立于特定的UI技术进行同步。在Windows窗体中,所有控件(包括表单)都是ISynchronizeInvoke
实现,您可以在同步业务对象时简单地传递表单。在WPF中,您必须自己实现interace,这是微不足道的,因为您只需要在Application.Current.Dispatcher.Invoke
方法中调用Invoke
(并分别对BeginInvoke
等执行相同操作。)。 / p>
依赖注入
对于依赖性注入,您不能将抽象直接传递给您的业务类,但是更高层代码(在您的情况下是UI)&#34;注入&#34;下层的依赖代码,它的类可以拾取和使用。有许多现有的依赖注入框架,但您可以自己实现一种简单的依赖注入形式,这在简单的场景中很有用。在您的业务逻辑(或两个层使用的独立框架)中,您可以定义:
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);
答案 1 :(得分:0)
自从提出这个问题以来,我已经知道可以Dispatch
一个任务由UI线程执行。
这样做是这样的:
Application.Current.Dispatcher.Invoke(() =>
{
_ulFileList.Add(Path.GetFileName(file));
});