I have a winforms application that was originally designed to be have different sequential functions triggered manually with buttons (4 buttons without corresponding backgroundworkers,4 buttons with corresponding backgroundworkers, 8 buttons total). Each button does some quick set up synchronously and then fires off a background worker to do the work asynchronously. This can be heavy work.
A decision was made to have an "express" option that does all the work of the 4 buttons with default options. Unfortunately I didn't design this as modular as I should have.
What I have done is have the ExpressButton call a 5th background worker, which in turn calls InvokeOnClick on each button in sequential order. Luckily the 4 non asynchronous buttons clicks get called first. I am using an AutoResetEvent to block the 5th backgroundworker while each sequential button is clicked.
Here is some pseudo code, buttons 1-4 don't call background workers, buttons 5-8 call backgroundworkers and _resetevent = new AutoResetEvent(false) in the global variables:
private void backgroundWorker5_DoWork(object sender, DoWorkEventArgs e)
{
ControlEventArgs automationcheck = new ControlEventArgs(expressButton);
InvokeOnClick(button1, null);
System.Threading.Thread.Sleep(500);
InvokeOnClick(button2, null);
System.Threading.Thread.Sleep(500);
InvokeOnClick(button3, null);
System.Threading.Thread.Sleep(500);
InvokeOnClick(button4, null);
System.Threading.Thread.Sleep(500);
InvokeOnClick(button5, automationcheck);
_resetevent.WaitOne();
_resetevent.Reset();
InvokeOnClick(button6, automationcheck);
_resetevent.WaitOne();
_resetevent.Reset();
InvokeOnClick(button7, automationcheck);
_resetevent.WaitOne();
_resetevent.Reset();
InvokeOnClick(button8, automationcheck);
_resetevent.WaitOne();
_statusBox.AppendText("Finished" + Environment.NewLine);
}
So here is the strange thing. In the UI I have 2 textboxes where users enter information.
In backgroundworker1 (corresponding above to button5), I can access the .Text property of the first TextBox.
However in backgroundworker2 (corresponding to button6), I cannot access the .Text property of the other TextBox.
I can access it in the button6 click event all the way up until I call RunWorkerAsync().
As soon as I'm in the backgroundworker2 trying to access the TextBox.Text freezes the program up. No exception, it just stops.
Here is my theory :
backgroundworker1 is called/run with no parameters from button5 click event
backgroundworker2 is called/run with parameters from button6 click event
By passing an objectlist in RunWorkAsync(params[]) am I causing it to NOT pass some context of the original form control?
The funny thing here is that there is another textbox on the main form called statusBox that I can still access in backgroundworker2, in fact that's what I've been using for debugging purposes.
So to recap.
Button 9
Backgroundworker 5
Button 1
Button 2
Button 3
Button 4
Button 5
Backgroundworker 1
Can access TextBox.Text here
Button 6
Backgroundworker 2
Can't access TextBox.Text here
Button 7
Backgroundworker 3
unsure
Button 8
Backgroundworker 4
unsure
Worst case scenario :
Since button6 still has access to the textbox, I can grab the text out and pass it in the params list for runworkerasync. However I'd still like to know why 1 backgroundworker can see a textbox on the mainform and another can't.
Do as Austin said and Invoke() all the calls.
You're hitting this exception (i don't care if it's consistent) just randomly, any call to UI from a worker thread may lead to an exception as only the main thread must access the UI.
My bet is when you access the .Text property for the first time the control itself does not need to redraw, but the second time it needs, so you will end with a cross-threading exception.
Related
Here's a C# code, What happens is when qsubmit button is clicked, program straight away displays "wait..!".
When I debug the program it is found that when I click and function executes textbox1.text = "Hello"; but doesn't updates textbox, it updates only when the control goes off the event function, when that happens value of textbox has already been changed to "wait..!". I want to know why it doesn't updates textbox instantly(If that would have done, I would have seen the text during Thread.Sleep())
private void Button_QSubmit_Click(object sender, EventArgs e)
{
textBox1.Text = "Hello";
Thread.Sleep(1000);
textBox1.Text = "Wait..!";
}
The UI thread is responsible to redraw the windows. So as long as you are doing this inside the UI Thread (e.g. a Button click event), the process is busy with your code and the window is not drawn.
A easy solution could be the use of an Timer. Just add an timer and in the button click you start it (e.g. you set itup to fire in 1 second).
The Timer Event then will simply set the Text.
https://msdn.microsoft.com/en-us/library/system.windows.forms.timer.aspx shows details about the Timer class.
You are hanging the UI Thread by calling Thread.Sleep from the Main Thread (UI), to update the text box you have to let the UI thread do its job outside your function to update the UI..anyway call Application.DoEvents() before the sleep. But calling Application.DoEvents() is a bad design
I have 2 forms. In form one I enter information and the second one works as a helper to get codes for the first one(using it with ShowDialog() method). Since the information retrieved in the second form comes from a database that is being remotely accessed sometimes it takes a few seconds so what i did is to move the lengthy method that loads the information into the DataGridView to a BackgroundWorker, the code in the DoWork is:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
this.Invoke((MethodInvoker)delegate
{
//Method to retrieve records
});
}
and start it in the form's Shown() method. Now, my problem is, that the first time an instance of the form is created it works perfect but from the first time on it shows up like this:
How can I fix this issue?
First of all read the documentation about types you use:
You must be careful not to manipulate any user-interface objects in your DoWork event handler. Instead, communicate to the user interface through the ProgressChanged and RunWorkerCompleted events.Source
Because of the this.Invoke(... you basically do all the fetching from database on the UI thread, and that's why your form isn't redrawn.
In order to fix this issue:
Remove this.Invoke and any other code that accesses UI controls from the DoWork event handler. In that handler you only want to fetch the records from database, and NOT update any UI.
Move your UI updating code to the RunWorkerCompleted event handler.
Last but not least, you may want to start your data loading not on the form's Shown event but rather Form.Load, because it precedes the Shown and is raised before the form is displayed.
I have a form that appears as shown in the attached image. I have two parts of the form that concern this question: the TabControl, and the Panel, as shown in the image. It should be noted that the panel is NOT within the TabControl.
My situation is that I have a thread that executes continuously when the button, displayed in melt-your-eyes green in the Panel, is clicked. The thread polls the device which I'm interfacing with and updates the controls in the "Status" GroupBox at the bottom of the TabControl. When the user clicks on a control in the TabControl (tabControl_Enter event), I trigger a ManualResetEvent which lets the thread finish its iteration so that I can perform the IO required by the clicked control. The code to to suspend the thread is as follows:
private void StopSynchThread()
{
synchWaitHandle.Reset();
//various UI changes
}
private void updateSynchStat()
{
while (true)
{
synchWaitHandle.WaitOne();
try
{
updateSynch();
}
}
What I would like to do is then restart the thread automatically, instead of by button press, as is currently done. What I'm trying to do is avoid having to restart the thread by conditionally calling StartSynchThread() within each of the "bazillion" UI event handlers. StartSynchThread() is defined as:
private void StartSynchThread()
{
synchWaitHandle.Set();
}
Is there a precedent or decent paradigm for handling this? Without any concept of how to do so, I was thinking that I could alter my function that performs the IO with the device to generate an event after it gets a response from the device, but that seems inelegant.
Any ideas? I appreciate your insights. Thanks.
If you really can fire it off with a simple button click, you ought to be able to just put a timer on the form that will periodically check for the right conditions and then "push" the button (call synchWaitHandle.Set();) automatically.
I have a Winforms App (.NET 3.X) That runs a method in a class to process some data. The method periodically raises a StatusUpdate event with a count of the number of items processed. I have a ToolStripStatuslabel on the Form that i would like to update with the count. The problem is that status label never updates with this count until the process is complete. Below is the code from the status update event handler
toolStripStatusLabel.Text = e.Count.ToString();
statusStrip.Refresh();
I think the problem is that the Refresh event is not firing because the processing method is being called from within a Button press event. I think there is a way to force the Refresh to process but I do not remember what it is.
My only other solution is to execute the processing in it's own thread.
Found the answer in another thread:
Call Application.DoEvents() after setting the label, but you should do all the work in a separate thread instead, so the user may close the window.
This is the command that I was thinking of...
Have you tried calling refresh on the label itself ?
toolStripStatusLabel.Refresh();
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.