Below is my coding:
Form2 msgForm;
private void button3_Click_1(object sender, EventArgs e)
{
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
msgForm = new Form2();
try
{
bw.RunWorkerAsync();
msgForm.ShowDialog();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
// Coding that transmit protocol and will last around 2 minutes.
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
msgForm.Close();
}
I use the background worker method everytime I click a button to transmit protocols that last around 2 minutes. During transmission, the From2 will show 'Please wait'.
But I have some problem using this coding. The problem is like, when I click the button the first time, it will transmit the protocol once. After that I click again which is the second time, it transmit the protocol twice. After that I click again which is the third time, it transmit the protocol 3 times.... And so on. The times of protocol of transmit increase each time I click the button.
Aren't that it will only run once the coding in void bw_DoWork everytime I click the button?
Is there something wrong with my coding?
You're appending an additional handler every time you click, and then it's run along with everything you added before, which stays where it is (because the object is still there, you're re-using it).
To solve this, you need to either:
Move the declaration of the background worker inside the method (so it's new every time, with only one DoWork handler
Like this:
private void button3_Click_1(object sender, EventArgs e)
{
BackgroundWorker bw = new BackgroundWorker();
// rest of your code
}
Move the .DoWork += ... which appends the handler in the constructor of the class
It mostly depends on if you use that worker somewhere else.
It looks like a new worker starts on every click.
To avoid this behaviour, check if the worker is busy before starting it again.
try
{
if (!bw.IsBusy)
bw.RunWorkerAsync();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
And you can disable the button while the backgroundworker does its job, by button3.Enabled = false; and re-enable it in the bw_RunWorkerCompleted method, letting the user understand he has to wait and cannot click again until the process is finished.
First make sure that your lists/Collections are clear before transmit codes.
Then Use BreakPoint in your source code and remember that your BackgroundWorker can't run twice or more because you use ShowDialog.
Check if the worker is busy:
if (!bw.IsBusy)
bw.RunWorkerAsync();
I would also disable the button and change the text to for example 'Running' while the process is being excecuted.
Use the bw_RunWorkerCompleted method event call to then re-enable the button and change the text. This method runs on the same thread as the UI so there are no cross thread issues.
On your design, why are you showing another form for the 'Please wait' notification? I would suggest updating a label on your existing form either before the async process starts (so not cross thread UI issues) or if you need to update after then you can use the following:
lblNotify.Invoke(new Action(() => lblNotify.Text = #"Please wait"));
The above will then allow you to run your request on the main thread.
Related
Hi I have been trying to add a button into my program that when you click the button it displays text in a label, waits so the user can read it, then exits the program. but if I run it and try it it only waits then exits without displaying text. sorry if that was a bad explanation I just got into coding. This is what I have.
private void button3_Click(object sender, EventArgs e)
{
label1.Text = "Text Here";
Thread.Sleep(500);
this.Close();
}
Call label1.Invalidate() to force the control to be redrawn. When you call Thread.Sleep, the UI thread will be blocked and not update.
If this doesn't work, try label1.Refresh() or Application.DoEvents();
private void button3_Click(object sender, EventArgs e)
{
label1.Text = "Text Here";
label1.Invalidate();
Thread.Sleep(500);
this.Close();
}
A more ideal solution would be to use a timer, another thread, or some kind of async event to run the code separately from your UI:
timer = new Timer();
timer.Interval = 500;
timer.Tick += (sender, e) => Close();
timer.Start();
or
new Thread(delegate() {
Thread.Sleep(500);
this.Close();
}).Start();
Also note that 500 milliseconds is a pretty short time, did you mean 5000 milliseconds, which is equivalent to 5 seconds? You may want to also take a look at Winforms: Application.Exit vs Enviroment.Exit vs Form.Close, as Close() closes the current window.
Instead of using Thread.Sleep which blocks the UI thread (and keeps it from updating with your text), its better to keep the UI responsive. This will keep the UI working and delay then close the application.
private void button3_Click(object sender, EventArgs e)
{
label1.Text = "Text Here";
Task.Delay(5000).ContinueWith((arg) => Application.Exit());
}
The Task is run in another thread, delays for the specified milliseconds (5000 or 5 seconds in this case) then continues with a call to close the application.
By the way, this.Close() works to close the application only if the form you are running it from is the "initial" form of the application. If you ran the same code from another child form, it would only close the form. The better thing to do if you want to actually close the application is to use the Application.Close() method. This gracefully closes the application. If you want to down-right terminate, you can use Environment.Exit(int).
I have a Console app that displays a WinForms Form.
In the Form, the user clicks button 1 and it runs a long subroutine. I want to have a button 2 that can kill the subroutine at any point. However, the UI freezes when I click button 1 until the subroutine has finished. How can I get the UI to not freeze?
Your long-running code is blocking the UI thread, so you can no longer click the second button, nor interact with the UI in any way until the code is finished executing.
You'll need to move your long-running code to a separate thread. There are various (and newer) ways of doing this, but one way is the BackgroundWorker. It's pretty easy to learn, and wraps some nice functionality, like cancelling the thread.
Here's a short WinForms app to demonstrate. You have to explicitly enable the ability for the thread to be cancelled. In this example, the while loop continues indefinitely, but checks every 100ms to see if there's a request for it to be cancelled. When you click the second button, the cancellation request is sent, and the thread ends.
public partial class Form1 : Form
{
private BackgroundWorker bg;
public Form1()
{
InitializeComponent();
bg = new BackgroundWorker
{
WorkerSupportsCancellation = true
};
bg.DoWork += (sender, args) =>
{
while (true)
{
Thread.Sleep(100);
if (bg.CancellationPending)
break;
}
MessageBox.Show("Done!");
};
}
private void button1_Click(object sender, EventArgs e)
{
bg.RunWorkerAsync();
}
private void button2_Click(object sender, EventArgs e)
{
bg.CancelAsync();
}
}
Following up on chouaib's comment, another nice thing about using the BackgroundWorker in a WinForms environment is that you can drop and drop it onto your designer, similar to a Menu, Timer, etc. You can then access its members in the "properties" panel, setting "WorkerSupportsCancellation" to true, subscribing to events, etc.
From your comment:
"is there a way to run this background process and be able to update the main userform? I keep getting the "cross-thread operation not valid control accessed from a thread other than the..." I want to run the long-running background operation, and have it update the main UI with text in a label (like a percentage of its progress)"
If you want to update the UI while the thread is running, you should do that from the ProgressChanged event. First, enable that option and subscribe to the event:
bg.WorkerReportsProgress = true;
bg.ProgressChanged += bg_ProgressChanged;
Then call ReportProgress() when you want to update the UI. You could pass back a percentage complete and some text, for instance:
bg.ReportProgress(50, "Almost there...");
Finally, update the UI from inside the event:
void bg_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var message = e.UserState.ToString();
var percent = e.ProgressPercentage;
lblStatus.Text = message + " " + percent;
}
You need to make it multithreaded as suggested in the comments. The older way of doing this was manage your own thread. Then along came the background worker (cheap and easy). Now a days you have other options such as the Task Library.
Remember - anything the runs on the UI thread prevents the UI from sending and receiving events until that operation is finished.
Look into the BackgroundWorker component
I am working on application that uses BackgroundWorker Thread. I have a button click event on which I'm doing following things
btnLocate_Click(Object sender, EventArgs e)
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += bw_DoWork;
bw.RunWorkerCompleted += bw_RunWorkerCompleted;
bw.RunWorkerAsync(lstNumbers.CheckedItems[0].ToString());
}
In the Background Worker do work event I'm adding some values in globally defined ObservableCollection like this
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
try
{
lock (locker)
{
_RecData.Add(new RecNumberData
{
// Some Values
});
}
}
In BackgroundWorker complete event I'm setting this collection as data source for grid and start a timer that create a new BackgroundWorker and do the same job again.
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
grid.DataSource = RecData;
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
timer1.Stop();
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += bw_DoWork;
bw.RunWorkerCompleted += bw_RunWorkerCompleted;
bw.RunWorkerAsync(lstNumbers.CheckedItems[0].ToString());
}
Now the code runs fine when first time BackgroundWorker runs. But when second time it runs after Timer ticking event, Exception raised at line Cross Thread Opeartion Detected.
_RecData.Add(new RecNumberData
{
// Some Values
});
What could be the cause?
Speculation without seeing more code, but I would suspect that:
When you are adding values to the collection the first time through, the collection isn't bound to the UI => no problem
The second time through it is bound to the UI (in the first complete event), so your background worker is attempting to update the UI.
The solution might be to create a second observable collection for your second BackgroundWorker. Or defer adding the results to the collection until the Completed event.
You are modifying a GUI object from outside the GUI thread. You shouldn't do that.
By setting RecData as the data source of a GUI object, every change to RecData will trigger a notification that will change the GUI object. After binding RecData you are changing RecData, and therefore your GUI object, from the background worker thread. Objects that are databound to a GUI object must not be modified by any thread other than the GUI thread.
You can use Control.BeginInvoke to enqueue an action on the GUI thread.
You're adding data to a variable that is used as a datasource to a graphical component (grid), you need to tell us what UI technology you're using (WPF? WInform?).
What's happening is you're doing something on a thread that isn't the one the UI runs on, and most UI technologies do not allow things that affect them running on another thread, there are mechanisms to work around this but they depend on the tech.
Most of the time you'll want to register the code that changes data the UI reads to run on a dispatcher (it will be queued and run on the main thread as soon as time is available)
I am trying to create a windows form that gets displayed for 2 seconds when triggerd by an event, and then closes automatically.
I have tried several options. This is my current code:
this.aletPopup.StartPosition = FormStartPosition.CenterScreen;
this.aletPopup.Show();
Thread.Sleep(2000);
this.aletPopup.Close();
This preforms the actions that I desire, however, when the form loads it does not load the label or image which is on the form. Instead, the area where the image and label are become transparent. My desired output is this:
I have also tried using this.aletPopup.ShowDialog();, which does display the graphics. However, the form will not close automatically when using this method.
EDIT: I am attempting to use
Michael Perrenoud's solution. However, I cannot get the form to close. I have a timer set at a 2000ms interval which is initally disabled. Am I overriding the OnShown correctly?
public AlertPopForm()
{
InitializeComponent();
}
private void closingTimer_Tick(object sender, EventArgs e)
{
closingTimer.Enabled = false;
this.Close();
}
private void AlertPopForm_OnShown(object sender, System.EventArgs e)
{
closingTimer.Enabled = true;
closingTimer.Start();
}
Instead, how about leveraging ShowDialog, and then using a Timer on the dialog form. In the Tick event of the Timer, close the form.
this.aletPopup.StartPosition = FormStartPosition.CenterScreen;
this.aletPopup.ShowDialog();
You could even pass the interval into the .ctor of the dialog form if you wanted some flexibility.
It's very important to note here that you'd need to leverage the OnShown override to actually Start the Timer so the form is in fact shown to the user.
The reason can be in Message Loop. When you block your thread by Thread.Sleep, it also blocks Message loop.
You can make like this:
this.aletPopup.StartPosition = FormStartPosition.CenterScreen;
this.aletPopup.Show();
for(var i = 0; i<= 200; i++)
{
Thread.Sleep(10);
Application.DoEvents();
}
this.aletPopup.Close();
DoEvents will process messages from message queue during that time.
When calling Thread.Sleep you're blocking the UI thread, thus preventing it from processing UI events.
You need to ensure that Close is called after 2 seconds without actually blocking the main thread. There are a number of ways of doing this, such as using a Timer, or something like Task.Delay:
aletPopup.StartPosition = FormStartPosition.CenterScreen;
aletPopup.Show();
Task.Delay(TimeSpan.FromSeconds(2))
.ContinueWith(t => aletPopup.Close(),
TaskScheduler.FromCurrentSynchronizationContext());
The reason this is happening, is that you are halting the thread that draws the form. So the form has time to display, but as it's being drawn, the thread is being stopped.
Easy enough to fix....
Add an event handler to the popup for the Load event with the following handler:
private async void handleLoad(Object sender, EventArgs args)
{
await Task.Delay(2000);
Close();
}
Remark
Because you used Show(), the user could always click around this popup. If this is undesirable, then use ShowDialog() instead.
Did you try a refresh to redraw the form?
this.aletPopup.StartPosition = FormStartPosition.CenterScreen;
this.aletPopup.Show();
this.alertPopup.Refresh();
Thread.Sleep(2000);
this.aletPopup.Close();
I have a click event which causes the phone to vibrate once a button is clicked. This generally works great, except sometimes the vibration doesnt stop until I completely close the application. I would like to give the application time to complete its vibration and then continue with the rest of the method, but I do not want to block the UI at all. How might I accomplish this
MainPage.xaml.cs
void newButton_Click(object sender, EventArgs e)
{
//Button vibration
if (Settings.EnableVibration.Value) //boolean flag to tell whether to vibrate or not
{
VibrateController.Default.Start(TimeSpan.FromMilliseconds(100));
//place vibration stop here?
}
this.NavigationService.Navigate(new uri("/NewPage.xaml", UriKind.Relate));
}
I have already tried VibrationController.Default.Stop(); but this completely eliminates the vibration all together. Is there a way to simply wait until after the vibration has completed to then navigate to a new page, or do whatever other action the method should perform? Any reccomendations or advice on this implementation or other suggestions?
You can use asynchrony to prevent blocking the UI. Rather than actually blocking the UI thread you need to schedule an action to happen again 100ms from now. Adding a continutation to a Task.Delay call can do just that:
void newButton_Click(object sender, EventArgs e)
{
Action navigate = () =>
this.NavigationService.Navigate(new uri("/NewPage.xaml", UriKind.Relate));
if (Settings.EnableVibration.Value) //boolean flag to tell whether to vibrate or not
{
VibrateController.Default.Start();
Task.Delay(100).ContinueWith(t =>
{
VibrationController.Default.Stop();
navigate();
});
}
else
navigate();
}
.NET wraps this functionality up conveniently in the BackgroundWorker class.
private void SomeMethod()
{
// Create backgroundworker
BackgroundWorker bw = new BackgroundWorker();
// Attach event handler
bw.DoWork += bw_DoWork;
// Run Worker
bw.RunWorkerAsync();
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
// Do background stuff here
}
It also has support for progress updates and triggers an event on completion, as far as I know, this functionality extends to windows phone. All of this is covered in the MSDN article.
I would guess what you want to do is call vibrate in the BackgroundWorker, and you can listen for the RunWorkerCompletedEvent which will fire when it is finished. Also, you can happily pause this "thread" and it will not interfere with the UI.