I'm trying to download some movie data in a BackgroundWorker thread, but when the background thread tries to modify ObservableCollection fields for the selected movie there is an error stating "This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread".
First I tried removing any UI elements such as TextBoxes before running the BackgroundWorker, which strangely didn't seem to work even though there were no objects left to synchronize changes.
I can get around this by using the answer here to send the changes to the UI thread but it means I'd have to flood my background thread with many lines like uiContext.Send(x => _matchObsCollection.Add(match), null); which would make my code a little messier than I'd like.
Ideally I'd like to remove the SynchronizationContext so that the UI thread would not try to interfere with the background thread, but again this isn't making any difference. Is there something wrong with what I'm trying to do here:
// Prevent synchronization with the UI thread.
var uiContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(null);
// Prepare the background worker for data download tasks.
BackgroundThread = new BackgroundWorker();
BackgroundThread.WorkerReportsProgress = true;
BackgroundThread.ProgressChanged += BackgroundThread_ProgressChanged;
BackgroundThread.DoWork += (f, arg) =>
{
DownloadMovieData(movie, uiContext);
};
You could try to store the result of the DownloadMovieData function on a temporary variable and add a RunWorkerCompleted event to your background worker to set the actual ObservableCollection to the value of the temporary variable you created.
Related
I am just starting to deal with STA/MTA issues, so apologies for the simplicity of the question. I couldn't find an answer I could actually understand at the bottom of the ladder here.
I am writing a plugin for another piece of software, and come to a point in a worker thread that I need to create some UI elements. I understand that I cannot do that from inside the worker thread since it is not an STA thread, and that I need to get back to the Main (or just another?) STA thread to create the UI elements. Some clarifications would help greatly.
Do all STA threads have the same 'rights', i.e. if the main thread is STA and creates a Window, adds some UI elements to it. Then spawns off another STA thread, and that second thread likewise creates some UI elements, are they doing it in the same 'space' (poor word choice, but I don't know what else to use) and can access each other's UI elements without causing death and destruction? Or do I need to explicitly jump back to the Main/Original STA thread and ONLY ever create UI elements from THAT (not just ANY) STA thread?
If that is the case (only 1 STA thread is allowed to make UI elements) how do I do that correctly? I have seen many posts that related to this but for some reason I can't quite catch what's going on, and would love a REAL simple answer.
Please no 'Here's a cool slick way of doing...' I just need the simple way of at the point of execution where I need some UI elements jumping back over to the main STA thread if that's what's necessary.
If it is not necessary, then I will just make that worker thread an STA thread and continue on my way, is that fair? Or am I courting disaster?
if the main thread is STA and creates a Window, adds some UI elements to it. Then spawns off another STA thread, and that second thread likewise creates some UI elements, are they doing it in the same 'space' [snip...] and can access each other's UI elements without causing death and destruction?
If Thread A and B are both STA, then they can each create and update their own UI elements, but not eachothers. Any other threads that want to affect the UI have to use one of the BeginInvoke style methods to ask the appropriate thread to do the update.
If it is not necessary, then I will just make that worker thread an STA thread and continue on my way, is that fair? Or am I courting disaster?
You may not be able to make the worker thread an STA thread if it's been set to MTA and initialized. You may have to make a new thread.
How do you do it? It seems like you want to use WPF (System.Windows.*) for your UI - so If the app that you are "plugging into" is also using WPF, you should be able to access it and re use it's UI thread. If not, you can make a new thread, and create a new Application on it and call Run. This should set up a dispatcher for you.
Something like this (pseudo code sort-of copied from some working code I have elsewhere)
Dispatcher dispatcher = null; // we 'get to' the UI thread via the dispatcher
if(Application.Current) { // re use an existing application's UI thread
dispatcher = Application.Current.Dispatcher;
} else {
var threadReadyEvent = new ManualResetEvent(false);
var uiThread = new Thread(() => {
Thread.CurrentThread.SetApartmentState(ApartmentState.STA);
var application = new Application();
application.Startup += (sender, args) => {
dispatcher = application.Dispatcher;
threadReadyEvent.Set();
};
// apps have to have a "main window" - but we don't want one, so make a stub
var stubWindow = new Window {
Width = 1, Height = 1,
ShowInTaskbar = false, AllowsTransparency = true,
Background = Brushes.Transparent, WindowStyle = WindowStyle.None
};
application.Run(stubWindow);
}){ IsBackground = true };
uiThread.Start();
threadReadyEvent.WaitOne();
threadReadyEvent.Dispose();
}
dispatcher.Invoke(() => {
// ask the UI thread to do something and block until it finishes
});
dispatcher.BeginInvoke(() => {
// ask the UI thread to do something asynchronously
});
and so forth
If a thread creates a control. Only this specific thread can interact with it, even if there are other STA threads.
In WinForms you would invoke a method on the control: Control.Invoke.In WPF you have the dispatcher to do it: Dispatcher.Invoke.
WinForms:
form1.Invoke(/* a delegate for your operation */)
WPF:
window1.Dispatcher.Invoke(/* a delegate for your operation */)
What you do is instead of changing an object in a "single apartment" you ask (invoke) the STA thread in control of it to do it (the delegate you invoke) for you. You also have BeginInvoke for doing it asynchronously.
I know I have seen an article or SO on this before, but cannot seem to find it now. I ran into a problem helping a coworker write a test that was checking UI actions that were occurring across multiple threads (I realize the problems here...that is not what I want to focus on at the moment :)). The code looks similar to this kind of pseudocode:
[RequiresSTA]
Test
{
var tb = new Textbox();
tb.DoSomethingAsyncAndThenUpdateTB() //This is done via tb.SetValue being called
}
...
DoSomethingAsyncAndThenUpdateTB()
{
var bw = new BackgroundWorker();
bw.DoWork += ...Do Stuff...
bw.RunWorkerCompleted += { tb.Text = "foo";}
}
The problem I am running into is that the OnComplete is throwing a cross thread exception. However, everything should have been created on the STA thread. I BELIEVE the problem is that UI elements get attached to their thread not on creation, but at a later point....and my textbox ends up attaching to a thread that isn't the STA? Or maybe the backgroundworker?
The question:
When does a UI element actually get attached to a thread?
So first off, there isn't just one STA thread. Any thread can be created as an STA thread when it's created. The UI thread needs to be an STA thread, but not all STA threads are UI threads.
Next, you have the issue that BackgroundWorker needs to have some way of knowing what the UI thread is. It's not magic that it's able to marshal certain events to the UI thread. What it does is it looks at the value of SynchronizationContext.Current in its constructor. It then captures the value of that context, and uses it later as it's definition of the context to post all non-work events to. If you create the BGW outside of the UI thread then it's not able to marshal back to the UI thread later.
If you have an STA thread that you are using as your UI thread, but you haven't set a value of SynchronizationContext.Current, then the BGW won't be able to do its thing.
As for the question of:
When does a UI element actually get attached to a thread?
In its constructor.
Each ui element implements DependencyObject, which implements abstract class DispatcherObject.. When DispatcherObject is created it assigns current dispatcher to the DispatcherObject..
Later on, whenever you access any property of the UI element, there is a validation that checks if you are accessing it from UI thread.
This si done in VerifyAccess
/// <summary>Enforces that the calling thread has access to this <see cref="T:System.Windows.Threading.DispatcherObject" />.</summary>
/// <exception cref="T:System.InvalidOperationException">the calling thread does not have access to this <see cref="T:System.Windows.Threading.DispatcherObject" />.</exception>
[EditorBrowsable(EditorBrowsableState.Never)]
public void VerifyAccess()
{
Dispatcher dispatcher = this._dispatcher;
if (dispatcher != null)
{
dispatcher.VerifyAccess();
}
}
If a calling thread is not the one associated with dispatcher, then exception is thrown
public void VerifyAccess()
{
if (!this.CheckAccess())
{
throw new InvalidOperationException(SR.Get("VerifyAccess"));
}
}
This is called Thread Affinity
I have an asynchronously running Task that fires an event when it's completed like this:
task.ContinueWith(() => {
if (MyEvent != null)
MyEvent(this, EventArgs.Empty);
}
The event handler then should create an instance of a WPF control. But when I try to do so, it causes an exception: The calling thread must be STA, because many UI components require this. Exception occurs in the class constructor, when calling method InitializeComponent().
As far as I know, usually accessing WPF controls from separate threads is handled using the Dispatcher.Invoke, and it always worked for me, so I tried it:
Dispatcher.Invoke(new Action(() =>
{
InitializeComponent();
}));
But in that case exception keeps occurring. How do I create an instance of a WPF control from a separate thread?
Or maybe it will be a better approach to marshal the completion event to the main UI thread. If yes, how can I do that?
You have to use a Dispatcher instance, which was associated with the UI thread. If you are writing something like this:
Dispatcher.Invoke(new Action(() =>
{
InitializeComponent();
}));
In the task body, you're using dispatcher of the calling thread, which can be a background thread from a pool.
Anyway, with tasks you shouldn't use Dispatcher directly. Use an appropriate task scheduler:
var ui = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.ContinueWhenAll(tasks.ToArray(),
result =>
{
// Put you UI calls here
}, CancellationToken.None, TaskContinuationOptions.None, ui);
where tasks is a sequence of tasks being executed with the default scheduler.
Calling InitializeComponent from the constructor on another thread seems like looking for trouble. The object isn't there yet (we're in the constructor)
Marshaling it back to the UI thread would normally do the trick but during the constructor looks like a bad idea to me.
If you want to initialize the control asynchronously just subscribe to the loaded event, so you know the object is there, spawn a thread that does some calculations/data retrieval and marshals the data back to the UI thread to display it.
I have done this in the past:
Dispatcher.Invoke(DispatcherPriority.Normal,
new Action(
delegate()
{
// Access control created by main thread
textBlock.Text = msg;
}
));
I tried following:
var task = new Task(() =>
{
for (int i=0; i<10; i++) {
//Create new Grid HERE
// Add Table with some dynamic data here..
// print the above Grid here.
}
});
task.ContinueWith((previousTask) =>
{
label.Content = printerStatus(); // will return "Out of Paper", "printing", "Paper jam", etc.
},
TaskScheduler.FromCurrentSynchronizationContext());
label.Content = "Sending to printer";
It returns following error: The calling thread must be STA, because many UI components require this..
The error occurs when it tries to create a new UI object Grid.
How can i fix this? Let me know if there is any other way arround!
Tasks use thread pool threads, which are in a MTA.
If you want a background STA thread, you will have to create one yourself and explicitly set the ApartmentState.
Thread t = new Thread( ... );
t.SetApartmentState( ApartmentState.STA );
t.Start();
You cannot create UI objects on different thread than the main UI thread because as soon as you add them to the UI, it tries to set the Parent property, and a thread in WPF cannot modify objects that were created on a different thread.
Instead, I'd recommend creating a list or collection of the Grid's Data on the 2nd thread, and binding it to the UI using something like an ItemsControl. This will keep all UI objects on the main UI thread, while background processing can be done on a background thread.
To update a UI object from a background thread, such as your status label, I'd recommend using the Dispatcher like lawrencealan's answer suggests. The Dispatcher is WPF's internal message queue for the main UI thread
Using the Dispatcher for the label and Invoke might help:
label.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Normal,
new Action(
delegate()
{
label.Content = printerStatus();
}
));
I am trying to reuse a UserControl and also borrow some logic that keeps track of progress. I'll try and simplify things. MyWindow.xaml includes a MyUserControl. MyUserControl has its own progress indicator (Formatting in progress..., Copying files..., etc.) and I'd like to mirror this progress somewhere in the MyWindow form. But, the user control has some logic I don't quite understand. I've read and read but I still don't understand the Dispatcher. Here's a summary of the logic in the user control that updates the progress.
this.Dispatcher.Invoke(DispatcherPriority.Input, (Action)(() =>
{
DAProgressIndicator = InfiniteProgress.AddNewInstanceToControl(StatusGrid, new SolidColorBrush(new Color() { A = 170, R = 128, G = 128, B = 128 }), string.Empty);
DAProgressIndicator.Message = MediaCardAdminRes.ActivatingCard;
ActivateInProgress = true;
}));
I thought I'd be smart and add an event to MyUserControl that would be called in the ActivateInProgress property set logic.
public bool ActivateInProgress
{
get
{
return _activateInProgress;
}
set
{
_activateInProgress = value;
if (ActivateInProgressHandler != null)
{
ActivateInProgressHandler(value);
}
}
}
I'm setting the ActivateInProgressHandler within the MyWindow constructor to the following method that sets the view model property that is used for the window's own progress indicator.
private void SetActivation(bool activateInProgress)
{
viewModel.ActivationInProgress = activateInProgress;
}
However, the window's progress indicator never changes. So, I'm convinced that the Dispatcher.Invoke is doing something that I don't understand. If I put a message box inside the SetActivation method, the thread blocks and the window's progress indicator is updated. I understand basic threads but this whole Dispatcher thing is new to me. What am I missing?
UPDATE: It seems to be working now. It turns out the progress was being updated so fast that it never got shown on the screen. But, I still would like to understand why the Dispatcher.Invoke was done (this was existing code that I didn't write). Why aren't the action contents done in line with the rest of the *.xaml.cs code?
Your last paragraph mentions threads twice, which raises the possibility that there are one or more background threads. But since you didn't mention what threads exist in the application, how they are created, how they interact, etc, I'll assume for the moment there is only one thread.
If the UI thread is the only thread, the problem is obvious: Your UI thread is busy running the task in progress, and doesn't take time to render the updated UI. If that's the problem, this will probably fix it:
viewModel.ActivationInProgress = activateInProgress;
Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle,
new Action(() => {}));
The BeginInvoke forces all Dispatcher operations above input priority to complete before the current thread continues.
The Dispatcher works on a queue. So it could be that the UI thread is blocking. You add more work in the queue via the Dispatcher but it will never get executed because the UI thread is blocking.
Maybe try this:
DispatcherFrame _frame = new DispatcherFrame();
Dispatcher.PushFrame(_frame);
This will put your work infront of the work already on the queue. So the UI thread will do the work and then block again.