ScottishTapWater
ScottishTapWater

Reputation: 4786

Adding to ObservableCollection<string> from threads

I have a series of ObservableCollection<string>s which are each bound to their own listbox. I have four worker threads, which each need to be able to add to these collections. The problem is, I can't add to these collections from the non-UI thread.

Normally (if I wasn't using data-binding) I'd use something like:

    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();
            }
        }
    }

However, since I'm not accessing the control directly, I don't know how I'd go about doing this, so any advice would be helpful.

My previous (incorrect attempt) looked like this:

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;
                            }
                      }

And my Thread structure is as such:

        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));

Where ConverterWorker() is a private void method.

Whilst researching my question beforehand I realised that having a cross thread operation for every iteration (that switch is inside a loop) might be very inefficient, so I'll store the changes locally and then update the UI in bigger batches, but I can work that out myself once I can access these collections.

Upvotes: 0

Views: 63

Answers (2)

Sefe
Sefe

Reputation: 14007

The problem you are facing is that thread synchronization is an UI issue, while the work you are doing is part of the business logic. In a well-designed system, the business logic has no knowledge of the UI and so it can not know the synchronization specifics. The solution to get out of the situation of "the business logic must synchronize through the UI logic, but it should have no knowledge of the UI" is devendency inversion.

Dependency inversion is a common design practice. In fact is is so common that it is the D in the SOLID principles. In your case it means that the dependency of the business logic on the UI synchronization logic is inversed, so the UI logic is dependent on the business logic, which is the correct design approach, since the UI layer is on top of the business layer.

It is recommended to put the business concerns into a different assembly than the UI concerns. The UI assembly references the business assembly, which in turn should have no references to any assemblies related to specific UI technology (such as any of the System.Web or System.Windows assemblies). That however makes Application.Current.Dispatcher.Invoke inaccessible to you. This is actually a good thing, because this is a part of the UI layer, which the business logic should have no knowledge of. There are two possible ways to solve the synchronization dilemma you are facing now:

UI independent abstraction

In the System.ComponentModel namespace you will find the ISynchronizeInvoke interface, which is used for your case where calls need to be synchronized. Although this interface is used extensively in Windows Forms, it is actually UI unspecific (and not defined in any UI specific assembly). So you can safely introduce a dependency to this inteface in your business class. Assuming the ConverterWorker method is defined in the MyBusinessClass class, you could pass an implementation of the interface in the constructor:

public class MyBusinessClass {
    private ISynchronizeInvoke syncInv;

    public MyBusinessClass(ISynchronizeInvoke syncInv) {
        this.syncInv = syncInv;
    }
}

You can now do all operations that need to be synchronized through syncInv and synchronize independently of the specific UI technology. While in Windows Forms all controls (inclding the form) are ISynchronizeInvoke implementations, you can simply pass the form when synchronizing the business object. In WPF you have to implement the interace yourself, which is trivial, since you only have to call Application.Current.Dispatcher.Invoke in the Invoke method (and do the same respectively for BeginInvoke etc.).

Dependency injection

With dependency incjection you don't pass the abstraction directly to your business class, but the higher layer code (in your case the UI) "injects" the dependency code to the lower layer, which its classes can pick up and use. There are plenty of existing dependency injection frameworks, but you can implement a simple form of dependency injection yourself, which serves you well in simple scenarios. In your business logic (or an independent framework used by both layers) you can define this:

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)
        }
    }
}

The business layer can get the ISynchronizeInvoke like this:

ISynchronizeInvoke syncInv = DependencyManager.GetDependency<ISynchronizeInvoke>();

The UI layer can inject the dependency like this (this has to happen before using business objects that rely on the dependency):

DependencyManager.AddDependency<ISynchronizeInvoke, MySyncInv>(mySyncInvImplementation);

Upvotes: 3

ScottishTapWater
ScottishTapWater

Reputation: 4786

Since asking this question I have worked out that one can Dispatch a task to be executed by the UI thread.

This is done like so:

Application.Current.Dispatcher.Invoke(() =>
      {
                 _ulFileList.Add(Path.GetFileName(file));
      });

Upvotes: 0

Related Questions