I am having a backgroundworker issue - I am new to threading so I am trying to get this done as painlessly as possible.
My main issue is the end users will only have .NET 4.0 so I cannot use await / async and been told BGW is the best thing for the framework I am using.
I have a "Please Wait" form with a gif animation that I would like to load while the datagridview is being populated. I need to somehow do a check to make sure it has finished populating to close the "Please Wait" down but am a bit stuck on how to achieve this.
public void btnSearch_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
Application.DoEvents();
try
{
this.TestDataTableAdapter.Fill(this.TesteDataData.TestDataTable, txtHotName.Text, ((System.DateTime)(System.Convert.ChangeType(txtDepartFrom.Text, typeof(System.DateTime)))), ((System.DateTime)(System.Convert.ChangeType(txtDepartTo.Text, typeof(System.DateTime)))), ((System.DateTime)(System.Convert.ChangeType(txtBookFrom.Text, typeof(System.DateTime)))), ((System.DateTime)(System.Convert.ChangeType(txtBookTo.Text, typeof(System.DateTime)))));
int RowC = TestDataTableDataGridView.RowCount;
if (RowC == 0)
{
MessageBox.Show(GlobVar.NoResults, "", MessageBoxButtons.OK, MessageBoxIcon.Hand);
}
}
catch (System.Exception exc)
{
MessageBox.Show
(
"Problem" +
exc.Message, "An error has occured", MessageBoxButtons.OK, MessageBoxIcon.Warning
);
}
finally
{
//pleaseWait.Close();
}
This is my button to load the data in to my DataGridView. And so far this is my DoWork event
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
pleaseWait.ShowDialog();
}
The finally will not work due to cross threading ( so comment out currently ) but I need to do a loop/check to find out if the DataGridView has been filled and action is complete to then close the DoWork. Or some how force it to jump to RunWorkerCompleted then I can just put a pleaseWait.Close(); in there instead.
Any suggestions please?
You must show your pleaseWait dialog in your main ui thread,not in the backgroundWorker1.DoWork, and hide it in the RunWorkerCompleted event of the backgroundWorker1. Is the this.TestDataTableAdapter.Fill part the one that should go in the backgroundWorker1.DoWork, so your code should look more or less like this:
public void btnSearch_Click(object sender, EventArgs e)
{
pleaseWait.ShowDialog();
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
try
{
this.TestDataTableAdapter.Fill(this.TesteDataData.TestDataTable, txtHotName.Text, ((System.DateTime)(System.Convert.ChangeType(txtDepartFrom.Text, typeof(System.DateTime)))), ((System.DateTime)(System.Convert.ChangeType(txtDepartTo.Text, typeof(System.DateTime)))), ((System.DateTime)(System.Convert.ChangeType(txtBookFrom.Text, typeof(System.DateTime)))), ((System.DateTime)(System.Convert.ChangeType(txtBookTo.Text, typeof(System.DateTime)))));
}
catch (System.Exception exc)
{
//You can't show a messagebox here,as it is not in the UI thread
}
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
pleaseWait.Close();
int RowC = TestDataTableDataGridView.RowCount;
if (RowC == 0)
{
MessageBox.Show(GlobVar.NoResults, "", MessageBoxButtons.OK, MessageBoxIcon.Hand);
}
}
Of course, in this code you have a problem, as the TestDataTableAdapter.Fill code won't work because you are trying to access some TextBoxes and you can't from another thread.
You have several solutions to this. You could use some variables to read the values before calling the backgroundworker, and accessing this variables instead of the TextBoxes. Or you could call the backgroundworker with parameters.
I recommend you to read more about BackGroundWorker, for example in MSDN. Or this question about sending parameters to a BackgroundWorker.
Related
I am making a GUI to a bigger program.
In order to include a Abort button, i had to use Backgroundworker.
void _oWorker_DoWork(object sender, DoWorkEventArgs e)
{
try
{
Starter.Handling(cBLog, cBSelC, cBSelT);
if (_oWorker.CancellationPending)
{
e.Cancel = true;
return;
}
}
catch (Exception exp)
{
MessageBox.Show(exp.Message,"Exception Caught:");
}
}
Now however I have stumbled upon a bigger problem, not beeing able to access the Textbox which is loaded as follows:
private void Form1_Load(object sender, EventArgs e)
{
// Instantiate the writer
_writer = new TextBoxStreamWriter(txtConsole);
// Redirect the out Console stream
Console.SetOut(_writer);
}
The Problem is, the TextBox has to stay the Console Output since I don't want to change the subprograms (which also contain while loops).
Any suggestions?
I have been trying many different thing and can't get this code to work. My code to stop backgroundworker then close window.
protected override void OnFormClosing(FormClosingEventArgs e)
{
if (bw.IsBusy)
{
bw.CancelAsync();
e.Cancel = true;
MessageBox.Show("close"); //Does show
return;
}
base.OnFormClosing(e);
}
During bw worker
if (worker.CancellationPending)
{
MessageBox.Show("Cancel"); // Does not show
//Cancel
e.Cancel = true;
}
On completed background worker
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Completed"); //Does not work
//Check if restart
if (bw_restart)
{
bw_restart = false;
bw.RunWorkerAsync();
}
//If it was cancelled
if (e.Cancelled)
{
this.Close();
}
//If error show error message
else if (e.Error != null)
{
MessageBox.Show(e.Error.ToString()); // Does not show
}
else //No errors or cancelled
{
MessageBox.Show(e.ToString()); //Does not shoiw
}
}
Cancel button
private void cancel_Click(object sender, EventArgs e)
{
bw.CancelAsync(); //Does not work :s
}
It does not close the window, the X when pressed does not do anything, I got it to close the form but not with stopping the background worker, driving me a bit mad. Link to code i got for this problem that not working: How to stop BackgroundWorker on Form's Closing event?
if (e.Cancelled)
That's fundamentally wrong. You can never be 100% sure that it will be set. Canceling a BGW is always a race condition, the BGW might have been busy exiting when you called its CancelAsync() method so never saw the CancellationPending set to true so never assigned e.Cancel = true in the DoWork event handler.
All you know for a fact is that mClosePending is reliable, since it was set to true on the UI thread. So always call Close() it it is set to true, regardless of the e.Cancelled state.
And yes, checking e.Error doesn't hurt either. But still check mClosePending.
As stated in my comment, your BackgroundWorker has ended due to an error, try adding the following at the top of your run worker completed. Once this error has been resolved your question will be more answerable.
if(e.Error != null)
MessageBox.Show(e.Error.toString());//Put a breakpoint here also
CancelAsync doesn't actually abort your thread or anything like that. It sends a message to the worker thread that work should be cancelled via BackgroundWorker.CancellationPending. Your DoWork delegate that is being ran in the background must periodically check this property and handle the cancellation itself.
Look at this:
private BackgroundWorker background;
private void Form1_Load(object sender, EventArgs e)
{
background = new BackgroundWorker();
background.WorkerSupportsCancellation = true;
background.DoWork += BackgroundOnDoWork;
background.RunWorkerCompleted += BackgroundOnRunWorkerCompleted;
background.RunWorkerAsync();
}
private void BackgroundOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs runWorkerCompletedEventArgs)
{
MessageBox.Show("stop");
}
private void BackgroundOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
{
// your doWork loop should check if someone don't call background.CancelAsync();
while (!background.CancellationPending)
{
// do something
}
}
private void ButtonClick(object sender, EventArgs e)
{
background.CancelAsync();
}
Lets say I have Task 1:
private void Task1()
{
//Here is some Code, could be any "longer" Task -
//For Example: Grab all words from a .txt File and fill in a List<String>
}
Then I have an other Task 2:
private void Task2(string word)
{
//So lets say theres a Label on my WinForm..
//Now While Task1 is grabbing the words, Task2 should fill a Label
//with the added 'word' (parameter) - (Task2 will be called from Task1
}
Actually I don't know how to make this possible, or whats the best way. On the UI I should be able to see the Label.Text changing (every word).. So I need to make a second Thread? How could I do this? Maybe someone could help me, cheers
UPDATE:
I tried it now with the Backgroundworker, but something seems to be false.. its actually not working, nothing happens on the form
Code:
public void CreateAndSaveAMatch(DateTime date) //That method is being called several times
{
//HERE IS CODE, WHICH CREATES AND SAVES A MATCH
// Start the asynchronous operation.
backgroundWorker1.RunWorkerAsync(date);
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
backgroundWorker1.ReportProgress(0, Convert.ToDateTime(e.Argument).ToShortDateString());
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = (string)e.UserState; //here on the Label I would like to show the Date
}
Ok, try this. This is a simple example that will show you how to solve your problem using BackgroundWorker. Also note that there are many other solutions. To use this example create a Form in a new project that only has a button and a label. Also note that this is a supplement of the other answers which were correct.
public partial class Form1 : Form
{
BackgroundWorker createAndSaveAMatchBGW;
public Form1()
{
InitializeComponent();
createAndSaveAMatchBGW = new BackgroundWorker();
createAndSaveAMatchBGW.DoWork += new DoWorkEventHandler(createAndSaveAMatchBGW_DoWork);
createAndSaveAMatchBGW.ProgressChanged += new ProgressChangedEventHandler(createAndSaveAMatchBGW_ProgressChanged);
createAndSaveAMatchBGW.RunWorkerCompleted += new RunWorkerCompletedEventHandler(createAndSaveAMatchBGW_RunWorkerCompleted);
createAndSaveAMatchBGW.WorkerReportsProgress = true;
}
private void button1_Click(object sender, EventArgs e)
{
createAndSaveAMatchBGW.RunWorkerAsync(DateTime.Now);
}
void createAndSaveAMatchBGW_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("BackgroundWorker finished");
}
void createAndSaveAMatchBGW_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = ((DateTime)e.UserState).ToString("ss");
}
void createAndSaveAMatchBGW_DoWork(object sender, DoWorkEventArgs e)
{
//BackgroundWorker does something for a 10 seconds, each second it Reports
BackgroundWorker bgw = (BackgroundWorker)sender;
DateTime dt = (DateTime) e.Argument;
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);
dt = dt.AddSeconds(1);
bgw.ReportProgress(0, dt);
}
}
}
And if you report from CreateAndSave... method only once per its execution, then you can use this code:
BackgroundWorker createAndSaveAMatchBGW;
public Form1()
{
InitializeComponent();
createAndSaveAMatchBGW = new BackgroundWorker();
createAndSaveAMatchBGW.DoWork += new DoWorkEventHandler(createAndSaveAMatchBGW_DoWork);
createAndSaveAMatchBGW.RunWorkerCompleted += new RunWorkerCompletedEventHandler(createAndSaveAMatchBGW_RunWorkerCompleted);
}
private void button1_Click(object sender, EventArgs e)
{
createAndSaveAMatchBGW.RunWorkerAsync(DateTime.Now);
}
void createAndSaveAMatchBGW_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
label1.Text = ((DateTime)e.Result).ToString();
}
void createAndSaveAMatchBGW_DoWork(object sender, DoWorkEventArgs e)
{
DateTime dt = (DateTime) e.Argument;
//you do something with your DateTime
dt = dt.AddDays(10);
e.Result = dt;
}
Use BackgroundWorker for reporting progress from first task. Drag this component from toolbox to your form, and subscribe to DoWork and ProgressChanged events. Also set property WorkerReportsProgress to true. Then start you first task asynchronously:
// this will execute code in `DoWork` event handler
backgroundWorker1.RunWorkerAsync();
Next - use userState object to pass processed words:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// grab words in a loop and report progress
backgroundWorker1.ReportProgress(0, word);
}
And last step - update label in ProgressChanged event handler
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text += (string)e.UserState; // this is your grabbed word
}
The simplest way to achieve this kind of thing is using BackgroundWorker.
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
BackgroundWorker automatically handles thread marshalling and provides events that allow you to update the UI. The event handlers run on the UI thread.
The things you do in Task1 could be moved into a BackgroundWorker, and the updates to the UI that you propose to do in Task2 can actually be in response to progress events from BackgroundWorker.
ProgressChangedEventArgs provides for user-defined data that could hold the current word.
However, Winforms (and indeed pretty much any UI) will not be able to keep up with a separate CPU thread just loading words from a file if you intend to show every word you load.
Task1 could be started on a separate thread.
You wouldn't actually need a Task2 unless there was some complex logic being performed to update the TextBox. You you really need to do is use TextBox.Invoke() to invoke the update on the UI Thread from Task1.
I've made a small app where Form is threaded (using BackgroundWorker), and in the form I'm calling a function QuitApplication in Program class when I want to quit.
The DoWork looks like this:
static void guiThread_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
while (true)
{
if (worker.CancellationPending == true)
{
e.Cancel = true;
break;
}
if (Program.instance.form != null)
{
Program.instance.form.UpdateStatus(Program.instance.statusText, Program.instance.statusProgress);
}
Thread.Sleep(GUI_THREAD_UPDATE_TIME);
}
}
and in the Form1 class i have this method attached to the closing of the window:
void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
Program.instance.SetStatus("Closing down...", 0);
Program.QuitApplication();
}
So what i want is to ensure that everything quits when I press the X on the window. However, the if( worker.CancellationPending == true ) never hits... why is this?
QuitApplication looks like this:
public static void QuitApplication()
{
Program.instance.guiThread.CancelAsync();
Application.Exit();
}
And Im using guiThread.WorkerSupportsCancellation = true
CancelAsync is setting the CancellationPending property, but then you immediately quit the application without giving the background thread a chance to detect that and shut down. You need to change your UI code to wait for the background thread to finish.
Personally, when I write apps like this, I make the form close button act like a Cancel button rather than quit immediately. It's a lot safer for the end user. For example:
private void abortButton_Click(object sender, EventArgs e) {
// I would normally prompt the user here for safety.
worker.CancelAsync();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
if(worker.IsBusy) {
// If we are still processing, it's not very friendly to suddenly abort without warning.
// Convert it into a polite request instead.
abortButton.PerformClick();
e.Cancel = true;
}
}
I'm working on a windows App in C#, I have a for-loop which update something in a loop, and I have 3 buttons on the form named "Stop,Pause,Resume". So the purpose is as same as the buttons named. Does anyone know how to do this?
Here is the Loop
private void btnCompleteAuto_Click(object sender, EventArgs e)
{
setGeneralValue();
for (int i = 1; i <= autoGridView.Rows.Count - 1; i++)
{
if (SRP == "Pause") // this is what I was thinking but it won't work
{ // it will step into end-less loop
do // how to stop this loop on "Resume" button click
{
}while(SRP!="Resume")
}
car = false;
try
{
MemberID = Convert.ToInt64(autoGridView.Rows[0].Cells["Member_ID"].Value);
DispID = Convert.ToString(autoGridView.Rows[0].Cells["Disp_Id"].Value);
Mobile = Convert.ToString(autoGridView.Rows[0].Cells["Mobile"].Value);
DueDate = Convert.ToString(autoGridView.Rows[0].Cells["Due_Date"].Value);
}
catch (Exception)
{
MessageBox.Show("Row Not Found");
}
AutoRecharge(network_name, pack_name, Mobile, Mobile, Convert.ToString(autoGridView.Rows[0].Cells["Rck_Amt"].Value), vendor_id, vendor_pwd, pack_id, oxinetwork_id);
autoGridView.Rows.RemoveAt(0);
}
}
Here are the 3 button events in which I'm setting a variable
private void btnPause_Click(object sender, EventArgs e)
{
SRP = "Pause";
}
private void btnStop_Click(object sender, EventArgs e)
{
SRP = "Stop";
autoGridView.DataSource = "";
}
private void btnResume_Click(object sender, EventArgs e)
{
SRP = "Resume";
}
The reason this doesn't work as you expect is this:
A Windows Forms application uses a single UI thread, which continually processes incoming messages from a queue. Any event handlers you attach to the events of a Windows Forms control get sent to this queue and processed by the UI thread as quickly as possible.
Your btnCompleteAuto_Click is one such handler. Once it starts, nothing else will be processed by the UI thread until it exits. Thus any other handlers you attach to other events (btnPause_Click, btnStop_Click, etc.) must wait their turn, as they will run on the same (UI) thread.
If you want pause/resume functionality, this has to be achieved on a separate thread.
A possible way to implement it might be to use a BackgroundWorker, as suggested by saurabh.
Here is a rough sketch of what your updated code might look like (I have not even attempted to compile this, let alone debug it; it's intended only as a basic outline of how you might accomplish this functionality).
You need to be aware, however, that accessing UI controls directly from a non-UI thread is a no-no. Use a mechanism such as the BackgroundWorker.ProgressChanged event to handle any UI updates that you need to happen based on activity on a non-UI thread.
ManualResetEvent _busy = new ManualResetEvent(false);
private void btnCompleteAuto_Click(object sender, EventArgs e)
{
if (!backgroundWorker.IsBusy)
{
_busy.Set();
btnAutoCompleteAuto.Text = "Pause";
backgroundWorker.RunWorkerAsync();
}
else
{
_busy.Reset();
btnAutoCompleteAuto.Text = "Resume";
}
btnStop.Enabled = true;
}
private void btnStop_Click(object sender, EventArgs e)
{
_busy.Set();
backgroundWorker.CancelAsync();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// for (something)
// {
_busy.WaitOne();
if (backgroundWorker.CancellationPending)
{
return;
}
// Do your work here.
// }
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
_busy.Reset();
btnAutoCompleteAuto.Text = "Start";
btnStop.Enabled = false;
}
After Reading your actual requirement in our comment , i would suggest that use Background worker class which supports cancellation of running process.
See here