Possible Duplicate:
How to block Winforms UI while background thread is running
i am using a C# WinForm application
I have a Save button on the screen where the data of the screen is saved to database.
what happens, when user clicks to button application goes to database and saves data. it takes some time.
but mean while if user again click on the Save button the Click event get catched and when the first Click return to main code (after saving database) the caught event get fired..
In short the click event get caught and fired when the thread returns from the first event ( I tried the scenario of enable/disable the button).
How can I stop this behavior.
Regards, Akhil
@Jalal: I tried this code with some modification as
private readonly object _userActivityLocker = new object();
private void btnSave_Click(object sender, EventArgs e)
{
if (System.Threading.Monitor.TryEnter(_userActivityLocker))
{
//note that any sub clicks will be ignored while we are here
try
{
DateTime dt = DateTime.Now;
Thread.Sleep(2000);
Debug.Print("FirstClick {0} Second Click {1}",dt.ToLongTimeString(), DateTime.Now.ToLongTimeString());
//here it is safe to call the save and you can disable the btn
Application.DoEvents();
}
finally
{
System.Threading.Monitor.Exit(_userActivityLocker);
//re-enable the btn if you disable it.
}
}
}
but when i rapidly click on button (i checked with 5 times rapid clicks) 5 click events has been fired and console window is showing
FirstClick 1:30:22 PM Second Click 1:30:24 PM FirstClick 1:30:24 PM Second Click 1:30:26 PM FirstClick 1:30:26 PM Second Click 1:30:28 PM FirstClick 1:30:28 PM Second Click 1:30:30 PM FirstClick 1:30:30 PM Second Click 1:30:32 PM
The problem is that your program is dead to the world while it is saving the data to the database. The user's mouse click is sitting in the message queue, waiting for your UI thread to come back to life. When it does, the button is no longer disabled so the Click event fires.
You can solve it by emptying the message queue before you re-enable the button so the click is processed while the button is disabled:
private void button1_Click(object sender, EventArgs e) {
button1.Enabled = false;
// Save data to database
//...
System.Threading.Thread.Sleep(2000);
Application.DoEvents(); // Empty the message queue
if (!button1.IsDisposed) button1.Enabled = true;
}
Do not skip the IsDisposed test, DoEvents is dangerous because it isn't selective about what events get processed. It will happily let the user close the main window while your code is still running.
But the better solution is to not let your UI thread go dead like this. Use a BackgroundWorker to perform the save on a worker thread. This will also avoid the ugly "Not Responding" ghost window that Windows puts up when your save takes more than a couple of seconds. It probably doesn't do this yet right now, but it will a year from now when the dbase has grown. You can re-enable the button in the BGW's RunWorkerCompleted event handler.
By enabling, then re-enabling again as you eluded to. What was the problem with this?
public void SaveButton_Click(..., ...)
{
this.SaveButton.Enabled = false;
Save();
this.SaveButton.Enabled = true;
}
using a System.Threading.Monitor
class will do the trick like:
private readonly object _userActivityLocker = new object();
private void btnSave_Click(object sender, EventArgs e)
{
new Thread(delegate()
{
if (System.Threading.Monitor.TryEnter(_userActivityLocker))
{
//note that any sub clicks will be ignored while we are here
try
{
//here it is safe to call the save and you can disable the btn
}
finally
{
System.Threading.Monitor.Exit(_userActivityLocker);
//re-enable the btn if you disable it.
}
}
}) { IsBackground = true }.Start();
}
To prove that changing the button to enable or disable state is not enough here a simple test:
Add a new form and add a button1 to it and inside the button1 click event handler write the following code:
private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
Console.WriteLine("First Message");
Thread.Sleep(2000);
Console.WriteLine("second Message");
button1.Enabled = true;
}
and then build and run the application, double click on the button1 and the result int the output window will be:
First Message
second Message
First Message
second Message
so we have to make sure only one click is executed even when double click or so and that accomplished simply by using the System.Threading.Monitor
Update: Note that you can use a Task "if C# 4.0", a ThreadPool.QueueUserWorkItem or BackgroundWorker as a substitute of Thread.
精彩评论