I have this in my App.Xaml:
public App()
{
_backgroundWorker = new BackgroundWorker();
_backgroundWorker.DoWork += new DoWorkEventHandler(DoBackgroundWork);
_backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BackgroundCompleted);
_backgroundWorker.RunWorkerAsync();
_splashView = new SplashView();
_splashView.Show();
}
The DoBackgroundWork method performs some database setup, and then the BackgroundCompleted event closes the _splashView and shows _mainView.
However, modifying anything in the _splashView from BackgroundCompleted causes a cross thread exception, which is what I though background workers were designed to fix. I'm guessing this has something to do with the way backgroundworker's work in App.Xaml. Maybe this is a bad way to do a splash screen?
The background worker uses the SynchronizationContext. This is going from memory, but I don't think it has been initialised in the App constructor.
Before you construct the BW check SynchronizationContext.Current is not null.
Also check SynchronizationContext.Current is the same before construction of BW and in the completed method.
If it's not, you'll need to move the code later in the process...
App.OnStartUp should be fine
Maybe this is a bad way to do a splash screen?
Unless I've misinterpreted your question, I don't think there's a need to do this to show a splash screen. Just select the image and in the Properties window click the BuildAction dropdown and select SplashScreen. Also, I think you can simplify your process by eliminating the BackgroundWorker, unless your data function takes a long time. I believe you added it to accomodate the showing/hiding of the splash screen.
There is no guarantee which thread the event handler of OnWorkCompleted will be used for execution.
See similar question BackgroundWorker OnWorkCompleted throws cross-thread exception
You have to use the Invoke or BeginInvoke methods to modify visual elements from a background thread. You can call this directly on the object whose properties you are modifying or use the Dispatcher.
EDIT: As per conversation with Adam
The SynchronizationContext has the desired effect for the OnWorkCompleted event handler to be run on the initial thread (not the BackgroundWorker's). http://msdn.microsoft.com/en-us/magazine/gg598924.aspx. (See Figure 2)
If the BackgroundWorker is created and run prior to the SynchronizationContext initialization, then the OnWorkCompleted will execute on possibly the same thread as the BackgroundWorker.
Thanks Adam.
Related
I am trying to create an animation that moves some images across the canvas in my applications layout.
The canvas is named layout which belongs to the main window and the timer is calling the event Animation.Clouds(layout, 1). When attempting this I am getting an error regarding the object is owned by another thread. This has left me to believe that the cause is the timer not being able to pass the context of layout, causing the error.
How would I solve this issue and pass layout to the timer in order for the animation to work?
use DispatcherTimer instead, it fire a tick in dispatcher thread it is created
The problem is that you can ONLY update a UI element, when you are on the UI thread. The Timer event gets invoke on a different thread.
In Windows.Forms you can use BeginInvoke. I'm sure WPF has something similar.
I want to have a loading form showing up while the main app connects to db and fetching initial data. Because this can take up 5-10 secs I want to inform the user that something is happening.
This is what I have come up with:
frmStart _frmStart = new frmStart();
Thread t = new Thread(() => _frmStart.ShowDialog());
t.Start();
objDAL = new DBManager();
objDAL.conSTR = Properties.Settings.Default.conDEFAULT;
PrepareForm();
t.Abort();
Is this the best way?
No, this doesn't solve the frozen UI problem, it merely papers it over. Imperfectly at that, the dialog's parent is the desktop and can easily appear underneath the frozen window and thus not be visible at all. And the dialog isn't modal to the regular windows since it runs on another thread. The failure mode when the user starts clicking on the non-responsive window is very ugly, those clicks all get dispatched when the UI thread comes back alive.
There are workarounds for that (you'd have to pinvoke SetParent and disable the main windows yourself) but that's just solving the wrong problem. You should never let the UI thread block for more than a second. Use a BackgroundWorker to do the heavy lifting, update the form with the query results in the RunWorkerCompleted event handler.
If you are using WinForms you may want to take a look at this A Pretty Good Splash Screen in C#. If you are using WPF you could take a look at this Implement Splash Screen with WPF.
I have a main GUI application, that does all of it's actual work in a referenced assembly. Right now, I DON'T do the work in a background worker, so it basically locks the main UI while it does it's processing. In my referenced assmbly, I added quite a few events to report back different progress back to the main UI form. On the main UI form, I update different text boxes with the values from those events. My question is, first of all, the processing appears to be much slower when throwing these events. So should I fire the events on a secondary thread (from the referenced assembly)? Should my original call to the referenced (static) assmebly be via a background worker? I'd like to report the different types of progress on a separate thread, just not sure which approach to take to have optimal performance.
Thanks
From your description it sounds like you would benefit from multithreading, as it would help keep the UI responsive.
And the easiest way to do this is to use a BackgroundWorker. Start by working through one of the many samples, then bite the bullet, and come back here if you have any problems.
In response to comment:
The best way to communicate from a BackgroundWorker worker thread to the main thread is to call the BackgroundWorker.ReportProgress method, which takes an optional object parameter userState which you can use to package up the data you want to communicate.
This causes the BackgroundWorker.ProgressChanged event to be raised on the main thread - and the data can be processed without the need for an explicit Invoke.
If you've already implemented events, you'll either have to do some rework to call ReportProgress instead of raising events, or implement some kind of adapter to handle the events and route them to ReportProgress method calls.
You could launch your process (the method on the other assembly) on a different thread, and handle the events raised by it on the main form.
Since the UI cannot be updated by another thread, you should wrap the code of those events on a this.Invoke().
Ex:
private void TheEventRaisedOnAnotherThread(object sender, EventArgs e)
{
_counter++;
this.Invoke(new MethodInvoker(delegate() { TextBox1.Text = _counter.ToString(); }));
}
I have a simple UserControl for database paging, that uses a controller to perform the actual DAL calls. I use a BackgroundWorker to perform the heavy lifting, and on the OnWorkCompleted event I re-enable some buttons, change a TextBox.Text property and raise an event for the parent form.
Form A holds my UserControl. When I click on some button that opens form B, even if I don't do anything "there" and just close it, and try to bring in the next page from my database, the OnWorkCompleted gets called on the worker thread (and not my Main thread), and throws a cross-thread exception.
At the moment I added a check for InvokeRequired at the handler there, but isn't the whole point of OnWorkCompleted is to be called on the Main thread? Why wouldn't it work as expected?
EDIT:
I have managed to narrow down the problem to arcgis and BackgroundWorker. I have the following solution wich adds a Command to arcmap, that opens a simple Form1 with two buttons.
The first button runs a BackgroundWorker that sleeps for 500ms and updates a counter.
In the RunWorkerCompleted method it checks for InvokeRequired, and updates the title to show whethever the method was originaly running inside the main thread or the worker thread.
The second button just opens Form2, which contains nothing.
At first, all the calls to RunWorkerCompletedare are made inside the main thread (As expected - thats the whold point of the RunWorkerComplete method, At least by what I understand from the MSDN on BackgroundWorker)
After opening and closing Form2, the RunWorkerCompleted is always being called on the worker thread. I want to add that I can just leave this solution to the problem as is (check for InvokeRequired in the RunWorkerCompleted method), but I want to understand why it is happening against my expectations. In my "real" code I'd like to always know that the RunWorkerCompleted method is being called on the main thread.
I managed to pin point the problem at the form.Show(); command in my BackgroundTesterBtn - if I use ShowDialog() instead, I get no problem (RunWorkerCompleted always runs on the main thread). I do need to use Show() in my ArcMap project, so that the user will not be bound to the form.
I also tried to reproduce the bug on a normal WinForms project. I added a simple project that just opens the first form without ArcMap, but in that case I couldn't reproduce the bug - the RunWorkerCompleted ran on the main thread, whether I used Show() or ShowDialog(), before and after opening Form2. I tried adding a third form to act as a main form before my Form1, but it didn't change the outcome.
Here is my simple sln (VS2005sp1) - it requires
ESRI.ArcGIS.ADF(9.2.4.1420)
ESRI.ArcGIS.ArcMapUI(9.2.3.1380)
ESRI.ArcGIS.SystemUI (9.2.3.1380)
Isn't the whole point of OnWorkCompleted is to be called on the Main thread? Why wouldn't it work as expected?
No, it's not.
You can't just go running any old thing on any old thread. Threads are not polite objects that you can simply say "run this, please".
A better mental model of a thread is a freight train. Once it's going, it's off on it's own track. You can't change it's course or stop it. If you want to influence it, you either have to wait til it gets to the next train station (eg: have it manually check for some events), or derail it (Thread.Abort and CrossThread exceptions have much the same consequences as derailing a train... beware!).
Winforms controls sort of support this behaviour (They have Control.BeginInvoke which lets you run any function on the UI thread), but that only works because they have a special hook into the windows UI message pump and write some special handlers. To go with the above analogy, their train checks in at the station and looks for new directions periodically, and you can use that facility to post it your own directions.
The BackgroundWorker is designed to be general purpose (it can't be tied to the windows GUI) so it can't use the windows Control.BeginInvoke features. It has to assume that your main thread is an unstoppable 'train' doing it's own thing, so the completed event has to run in the worker thread or not at all.
However, as you're using winforms, in your OnWorkCompleted handler, you can get the Window to execute another callback using the BeginInvoke functionality I mentioned above. Like this:
// Assume we're running in a windows forms button click so we have access to the
// form object in the "this" variable.
void OnButton_Click(object sender, EventArgs e )
var b = new BackgroundWorker();
b.DoWork += ... blah blah
// attach an anonymous function to the completed event.
// when this function fires in the worker thread, it will ask the form (this)
// to execute the WorkCompleteCallback on the UI thread.
// when the form has some spare time, it will run your function, and
// you can do all the stuff that you want
b.RunWorkerCompleted += (s, e) { this.BeginInvoke(WorkCompleteCallback); }
b.RunWorkerAsync(); // GO!
}
void WorkCompleteCallback()
{
Button.Enabled = false;
//other stuff that only works in the UI thread
}
Also, don't forget this:
Your RunWorkerCompleted event handler should always check the Error and Cancelled properties before accessing the Result property. If an exception was raised or if the operation was canceled, accessing the Result property raises an exception.
It looks like a bug:
http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=116930
http://thedatafarm.com/devlifeblog/archive/2005/12/21/39532.aspx
So I suggest using the bullet-proof (pseudocode):
if(control.InvokeRequired)
control.Invoke(Action);
else
Action()
The BackgroundWorker checks whether the delegate instance, points to a class which supports the interface ISynchronizeInvoke. Your DAL layer probably does not implement that interface. Normally, you would use the BackgroundWorker on a Form, which does support that interface.
In case you want to use the BackgroundWorker from the DAL layer and want to update the UI from there, you have three options:
you'd stay calling the Invoke method
implement the interface ISynchronizeInvoke on the DAL class, and redirect the calls manually (it's only three methods and a property)
before invoking the BackgroundWorker (so, on the UI thread), to call SynchronizationContext.Current and to save the content instance in an instance variable. The SynchronizationContext will then give you the Send method, which will exactly do what Invoke does.
The best approach to avoid issues with cross-threading in GUI is to use SynchronizationContext.
I want to call ShowDialog() when a keyboard hook event is triggered, but I'm having some difficulties:
ShowDialog() blocks, so I can't call it from the hook triggered event, because it will block the OS.
I can start a new thread and call ShowDialog() from there, but I get some nasty exception. I guess I can't call ShowDialog() in any other thread.
I can start a timer: in the next 50 milliseconds call ShowDialog() (which is a nasty hack BTW, and I rather not do this). But then the timer fires in a new thread, and then I run into the same problem explained in the previous bullet.
Is there a way?
The problem may be that you are trying to put UI in a non-UI thread. Make your event fire from another thread and invoke the method that runs ShowDialog() from your UI thread.
Essentially, you want to keep your UI on the UI thread and move anything else to a back ground thread.
Check out Gekki Software for some details (there are zillions of others - this just happens to be the first one I found in research archives).
I'm not sure about ShowDialog, but whenever you get an exception when trying to do something with the UI in a background thread, it means you should use the UI dispatcher.
Try calling the BeginInvoke method (if you are on Windows Forms) of any UI object you control with a delegate that calls the showdialog.
Also, make sure to try (before this) passing a reference to a valid owner in the show dialog method.
Try this:
void MyKeyboardHookHandler(...)
{
WindowsFormsSynchronizationContext.Current.Post(state =>
{
Form f = new Form();
f.ShowDialog();
}, null);
}
You really should be able to show the dialog from a KeyPress type event.
Also, if you use ShowDialog() from another thread, it will not be modal (no parent). It would be the same as using Show().
Without the "nasty exception" it's hard to tell what's going on. I would assume it's because your thread isn't an STA thread, and the UI objects are throwing the exception when they get instantiated. Set your new thread's apartment model to be STA instead of MTA and see if that helps.
And if you don't know what the difference is, you should do some reading up, for instance Multithreaded Apartments (MSDN).
ShowDialog() will block your application's thread, but that's what it's supposed to do. If you don't want the form blocking your application, call Show() instead.
ShowDialog() will not "block the OS", so don't be reluctant to use it.