从线程添加到ObservableCollection <string>

时间:2017-03-02 17:06:28

标签: c# wpf multithreading collections wpf-controls

我有一系列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更大的批次,但是一旦我可以访问这些集合,我就可以自己解决这个问题。

2 个答案:

答案 0 :(得分:3)

您面临的问题是线程同步是一个UI问题,而您正在进行的工作是业务逻辑的一部分。在设计良好的系统中,业务逻辑不了解UI,因此无法知道同步细节。摆脱业务逻辑的情况的解决方案必须通过UI逻辑同步,但它应该不知道UI&#34;是堕落的倒退。

依赖倒置是一种常见的设计实践。事实上它是如此常见,以至于SOLID principles中的 D 。在您的情况下,这意味着业务逻辑对UI同步逻辑的依赖性被反转,因此UI逻辑依赖于业务逻辑,这是正确的设计方法,因为UI层位于业务层之上。

建议将业务问题放在与UI关注点不同的程序集中。 UI程序集引用业务程序集,而该程序集又不应引用与特定UI技术相关的任何程序集(例如任何System.WebSystem.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));
      });