3

My application has 2 threads - the main UI thread, and another thread.

The application collects a List of file names on the separate thread. I then want this list to be displayed in my GUI. The program works as desired until the thread completes. It results in the following error

"The calling thread must be STA, because many UI components require this."

The following code is run, and as you can see it takes 2 Action Parameters - one for AddCurrentFile and one called Complete. During the duplication.GetDuplicateList() it invokes the 2 methods.

        this._duplicatesThread = new Thread(() =>
        {
            try
            {
                duplication.GetDuplicateList(new Action<string>(AddCurrentFile), new Action<List<Duplicate>>(Complete));
                CreateParent();
            }
            catch (Exception ex)
            {
                string s = ex.ToString();
                throw;
            }
        });

The AddCurrentFile provides a value to my property, and using the INotifyProperty my GUI updates as desired:

private void Complete(List<Duplicate> duplicateList)
{
   this._duplicateList = duplicateList;
}

The issue is when I called the CreateParent() as this creates a new UserControl - I then get The calling thread must be STA, because many UI components require this. But this occurs during the constructor of the UserControl. The following code is where it fails

    private void CreateParent()
    {
        ObservableCollection<DuplicateControl> parent = new ObservableCollection<DuplicateControl>();

        foreach (var duplicate in this._duplicateList)
        {
            DuplicateControl ic = new DuplicateControl();//This fails as soon as I enter the constructor. The DuplicateControl uses the UserControl as a base class
            parent.Add(ic);
        }
        this.ParentDuplicate = parent;
    }

So, to appease the thread, within the CreateParent() method I added this._duplicatesThread.Join;

However, I think (and please do correct/agree with me) what is happening at this point is because I'm joining within the thread, it's effectively cancelling the non-UI thread, therefore stopping at that point of it's process! As such, it never executes the line after this._duplicatesThread.Join;

Now, I'm hoping this isn't better asked at Programmers, but, I get the feeling the issue here is more of my design than the code (although I'd be delighted to know that both the pattern and my code is rubbish he he).

My question is, since I can't do this via the new Thread (as it's not a UI thread), what do I need to do? It's almost as if I need a delegate like NewThread.FinishedExecuting += DoThisThing()

Dave
  • 8,163
  • 11
  • 67
  • 103
  • 2
    Sounds almost like you just need to call `.Invoke()` on the UI element from your other thread. You shouldn't access UI components from a thread separate from the one that created it. That said, you can always marshal calls to the UI thread by using `.Invoke()`, which most UI components have. – sircodesalot Feb 11 '14 at 17:58
  • Simple example here: http://msdn.microsoft.com/en-us/library/757y83z4.aspx – sircodesalot Feb 11 '14 at 18:05
  • I'm sorry @sircodesalot, you're right - I had an error in my code, which I've updated. The exception is thrown in the constructor! So, at this stage it's just creating an object, it's not 'assigning' the object to a control. – Dave Feb 11 '14 at 18:07
  • @DaveRook rule is simple: You can not deal with UI elements within other threads (directly). – L.B Feb 11 '14 at 18:12
  • Yeah, everything UI related has to be done on the UI thread. That said, you can pass `Action`s to the UI thread using `.Invoke()` – sircodesalot Feb 11 '14 at 18:14
  • @L.B, wow... OK. I thought that creating a control (and not actually using it wouldn't have caused this - but you're saying that the thread cannot deal with a UI element at all (even constructing a UI element which is never used). – Dave Feb 11 '14 at 18:18
  • Well, so the issue you're running into is slightly different (only slightly though). When a UI element is created (assuming WPF here), it must be created on a thread that uses a Single Apartment (old COM concept). IE, when `static void Main()` is called it must be tagged with `[STAThread]` (take a look here: http://stackoverflow.com/questions/3584434/stathread-missing-but-it-is-there) This ensures that the thread runs out of a single apartment (which WPF requires). – sircodesalot Feb 11 '14 at 18:38
  • 1
    That said, even if you create a component on a separate thread which runs out of a single apartment (as required by WPF), you'll never be able to join that component with the rest of your UI because as soon as WPF tries to update it from the original UI thread, it will fail for the reasons outlined above. In short, you should use one UI thread, and then marshal calls to the UI thread through `.Invoke()`. – sircodesalot Feb 11 '14 at 18:40
  • 1
    Think about it like this, when a control is created, it logs which thread created it, and then every time the control is accessed, it does a verify check to make sure it's only being accessed by that same thread. – sircodesalot Feb 11 '14 at 18:42
  • @sircodesalot, now that last comment has really helped (although all your comments are excellent)... – Dave Feb 11 '14 at 19:10

2 Answers2

3

You should have separated the ViewModel from the View (UI) and use WPF data-binding to bind them together. It's actually possible to update the ViewModel objects from a background worker thread, the WPF framework will automatically marshal INotifyPropertyChanged.PropertyChanged notifications for the data-bound UI controls (at least, in .NET 4.5, AFAIK). This way, the controls will be updating themselves automatically on the main UI thread, as desired.

However, I'd rather treat the ViewModel in the same way as a UI element, i.e., manually marshal all ViewModel updates from the background thread to the main UI thread. This can be done with Dispatcher.Invoke (synchronously) or Dispatcher.InvokeAsync (asynchronously, recommended).

It's also possible to use Progress<T> pattern to propagate updates to the UI thread. Here is a related question/answer:

How to execute task in the wpf background while able to provide report and allow cancellation?

Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • Ah, the background worker thread did everything, just like that - it even had the complete delegate I was hoping for!! – Dave Feb 18 '14 at 15:35
0
Thread thread = new Thread(MethodWhichCallesTheConstructor);
        thread.SetApartmentState(ApartmentState.STA); //Set the thread to STA
thread.Start(); 
thread.Join(); //Wait for the thread to end
Tamir Vered
  • 10,187
  • 5
  • 45
  • 57
  • 1
    This is cool, but now you have two problems. If you create a control on this thread, how do you attach it to the rest of the UI on the main UI thread? – sircodesalot Feb 11 '14 at 21:45
  • You can still point ti this object from the main ui thread but basicly the whole ui needs to run on the sta. Add [STAThread] above your main ui class – Tamir Vered Feb 11 '14 at 22:21