I have written this code for a Message Receiving class that uses a backgroundworker to check for new files in a directory (the files are SMS messages received from users that are updating continuously). If the directory is not empty, I send an acknowledgement message to every new SMS and start the worker again.
public MessageReceiving()
{
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if(e.Result == true)
{
SendAcknowledgement();
if(!bw.IsBusy)
bw.RunWorkerAsync();
}
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
bool flag = false;
while (flag.Equals(false))
{
string path = #"C:\SMS";
if (Directory.GetFiles(path).Length > 0)
{
e.Result = true;
flag = true;
}
else
{
e.Result = false;
}
}
}
I initialize the worker from the main thread once -
MessageReceiving mr = new MessageReceiving();
mr.bw.RunWorkerAsync();
I did all this to allow me to send messages synchronously to users - As soon as a user send an SMS, I send him an ACK. The problem is that the user is getting multiple ACKs even on sending one SMS - why is this happening? I have thought of every possibility, but to no avail!
Consider what happens when this starts:
You start running bw_DoWork
That then tight-loops (not a good idea to start with) until it finds a file
The background worker completes, and you send an acknowledgement
You then immediately run the background worker again... which will find the file again unless you've deleted the file during SendAcknowledgement. Have you?
I suspect what you really want instead of any of this is a FileSystemWatcher by the way. Also note that just because a file is present doesn't mean that it's finished being written to yet, or that you can read it.
Additionally, your tight loop can be made a lot simpler:
void bw_DoWork(object sender, DoWorkEventArgs e)
{
string path = #"C:\SMS";
while (!e.Result)
{
e.Result = Directory.GetFiles(path).Any();
}
}
and I would likewise change
if (e.Result == true)
to
if (e.Result)
(Assuming e.Result is typed as a bool; if it's not, your current code has other problems.)
It depends on code not shown.
You need at most 1 thread scanning the files. Then somewhere during the processing you have to remove or rename the files. In your case this should happen in SendAcknowledgement, and the Bgw should not be restarted before it all replies have been sent.
It would be better to use a rename the files early and push them in a queue. Or process them directly after finding a file inside DoWork. SendAcknowledgement(fileName) looks more logical.
Currently your SendAcknowledgement() runs in the main thread, that may not be what you want.
Related
I asked in a previous question how to "Threading 2 forms to use simultaneously C#".
I realize now that I was not explicit enough and was asking the wrong question.
Here is my scenario:
I have some data, that I receive from a local server, that I need to write to a file.
This data is being sent at a constant time rate that I cant control.
What I would like to do is to have one winform for the initial setup of the tcp stream and then click on a button to start reading the tcp stream and write it to a file, and at the same time launch another winform with multiple check-boxes that I need to check the checked state and add that info simultaneously to the same file.
This processing is to be stopped when a different button is pressed, closing the stream, the file and the second winform. (this button location is not specifically mandatory to any of the winforms).
Because of this cancel button (and before I tried to implement the 2nd form) I used a background worker to be able to asynchronously cancel the do while loop used to read the stream and write the file.
private void bRecord_Click(object sender, EventArgs e)
{
System.IO.StreamWriter file = new System.IO.StreamWriter(AppDomain.CurrentDomain.BaseDirectory + DateTime.Now.ToString("yyyy-dd-M--HH-mm-ss") + ".xml", true);
data_feed = client.GetStream();
data_write = new StreamWriter(data_feed);
data_write.Write("<SEND_DATA/>\r\n");
data_write.Flush();
exit_state = false;
string behavior = null;
//code to launch form2 with the checkboxes
//...
worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.DoWork += new DoWorkEventHandler((state, args) =>
{
do
{
int var = data_feed.ReadByte();
if (var != -1)
{
data_in += (char)var;
if (data_in.IndexOf("\r\n") != -1)
{
//code to check the checkboxes state in form2
//if (form2.checkBox1.Checked) behavior = form2.checkBox1.Text;
//if (form2.checkBoxn.Checked) behavior = form2.checkBoxn.Text;
file.WriteLine(data_in + behavior);
data_in = "";
}
}
}
while (exit_state == false);
});
worker.RunWorkerAsync();
}
private void bStop_Click(object sender, EventArgs e)
{
exit_state = true;
worker.CancelAsync();
}
I hope I've been clearer now.
I not experienced in event programming and just started in C# so please try to provide some simple examples in the answers if possible.
At first would it be enough to use one Winform? Disable all checkboxes, click a button which enables the checkboxes and start reading the tcpstream? If you need two Forms for other reasons let me know, but i think this isn't needed from what i can see in your question.
Then i would suggest you to use the Task Library from .Net. This is the "modern" way to handle multithreading. BackgroundWorker is kind of old school. If you just able to run on .Net 2.0 you have to use BackgroundWorker, but don't seem to be the case (example follows).
Further if you want to cancel a BackgroundWorker operation this isn't only call CancelAsync();. You also need to handle the e.Cancelled flag.
backgroundWorker.WorkerSupportsCancellation = true;
private void CancelBW()
{
backgroundWorker.CancelAsync();
}
private void backgroundWorker_DoWork += ((sender, args)
{
//Handle the cancellation (in your case do this in your loop for sure)
if (e.Cancelled) //Flag is true if someone call backgroundWorker.CancelAsync();
return;
//Do your stuff.
});
There is no common way to directly cancel the backgroundWorker
operation. You always need to handle this.
Now let's change your code to the modern TAP-Pattern and make some stuff you want to have.
private void MyForm : Form
{
private CancellationTokenSource ct;
public MyForm()
{
InitializeComponent();
checkbox1.Enable = false;
//Disable all checkboxes here.
ct = new CancellationTokenSource();
}
//Event if someone click your start button
private void buttonStart_Click(object sender, EventArgs e)
{
//Enable all checkboxes here
//This will be called if we get some progress from tcp
var progress = new Progress<string>(value =>
{
//check the behaviour of the checkboxes and write to file
file.WriteLine(value + behavior);
});
Task.Factory.StartNew(() => ListenToTcp(ct, progress as IProgress<string)); //starts the tcp listening async
}
//Event if someone click your stop button
private void buttonStop_Click(object sender, EventArgs e)
{
ct.Cancel();
//Disable all checkboxes (better make a method for this :D)
}
private void ListenToTcp(CancellationToken ct, IProgess<string> progress)
{
do
{
if (ct.IsCancellationRequested)
return;
int temp = data_feed.ReadByte(); //replaced var => temp because var is keyword
if (temp != -1)
{
data_in += (char)temp;
if (data_in.IndexOf("\r\n") != -1)
{
if (progress != null)
progress.Report(data_in); //Report the tcp-data to form thread
data_in = string.empty;
}
}
while (exit_state == false);
}
}
This snippet should do the trick. I don't test it so some syntax error maybe occur :P, but the principle will work.
The most important part is that you are not allowed to access gui
components in another thread then gui thread. You tried to access the
checkboxes within your BackgroundWorker DoWork which is no possible
and throw an exception.
So I use a Progress-Object to reuse the data we get in the Tcp-Stream, back to the Main-Thread. There we can access the checkboxes, build our string and write it to the file. More about BackgroundWorker vs. Task and the Progress behaviour you can find here.
Let me know if you have any further questions.
How can I fix this issue ?
I am expecting the progressbar to load during process untill process it is done
Here is my code:
private void btnProcess_Click(object sender, EventArgs e)
{
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged;
backgroundWorker.DoWork += backgroundWorker_DoWork;
backgroundWorker.RunWorkerAsync();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//start transaction
DoTransaction();
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
}
My transaction function:
private void DoTransaction()
{
string pathIdentifier;
pathIdentifier = func.checkthePathFile();
if (pathIdentifier == null)
{
MessageBox.Show("Path has no yet been specified!");
}
else
{
//create xml base from user inputs
XElement transactXML = new XElement("Transaction",
new XElement("CardNumber", txtCardNum.Text.Trim()),
new XElement("ExpireDate", txtExpDate.Text.Trim()),
new XElement("Cardtype", txtCardType.Text.Trim())
);
//save xml to a file
transactXML.Save(pathIdentifier + "/sample.xml");
}
}
How is the runtime supposed to know how far along your process is?
You need to tell it by calling backgroundWorker.ReportProgress from the background operation. No magic here.
MSDN: http://msdn.microsoft.com/en-us/library/ka89zff4.aspx
Break down your process into meaningful chunks and ReportProgress whenever it makes sense to do so.
public void DoTransaction()
{
part1();
backgroundWorker.ReportProgress(25);
part2();
backgroundWorker.ReportProgress(50);
part3();
backgroundWorker.ReportProgress(75);
part4();
backgroundWorker.ReportProgress(100);
}
Edit Based on Posting of Transaction() function
If you are not confident in writing multithreaded programs, then do not attempt to write multithreaded programs, even with the help of a BackgroundWorker which tries to abstract some of those details away from you.
A few issues:
Your provided Transaction() method attempts to launch a MessageBox and read the Text property of various controls from the background thread. This is going to cause problems as the runtime typically throws an Exception when UI elements are accessed from a thread other than the one which created them.
If you really want to do the XML saving in the BackgroundWorker, you should validate the filename and directory, and save the Text properties to an intermediate object before setting up the BackgroundWorker and calling RunWorkerAsync.
Furthermore, in my opinion, your Transaction method is not going to be time intensive enough to truly warrant a background thread. Even a relatively old PC will be able to create and save a 15 element XML file faster than you can blink. The runtime will probably waste more time marshalling data between the threads than it would to simply write the file out to disk. Just do your work in the button click event handler.
needs some reference to the BackgroundWorker instance.pass the reference to the class when instantiating it.
instantiate like this
BackgroundWorker worker = sender as BackgroundWorker;
then call like this
`worker.ReportProgress(...)`
I'm trying to use a Background Worker in a WPF application. The heavy lifting task uses WebClient to download some HTML and parse some info out of it. Ideally I want to do that downloading and parsing without locking the UI and placing the results in the UI once it's done working.
And it works fine, however, if I quickly submit the "download and parse" command, I get the error:
This BackgroundWorker is currently busy and cannot run multiple tasks
concurrently
So I did some Googling and it seems that I can enable the .WorkerSupportsCancellation property of the background worker and just .CancelAsync(). However, this doesn't work as expected (canceling the current download and parse).
I still get the above error.
Here's my code:
//In window constructor.
_backgroundWorker.WorkerSupportsCancellation = true;
_backgroundWorker.DoWork += new DoWorkEventHandler(_backgroundWorker_DoWork);
_backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_backgroundWorker_RunWorkerCompleted);
//Declared at class level variable.
BackgroundWorker _backgroundWorker = new BackgroundWorker();
//This is the method I call from my UI.
private void LoadHtmlAndParse(string foobar)
{
//Cancel whatever it is you're doing!
_backgroundWorker.CancelAsync();
//And start doing this immediately!
_backgroundWorker.RunWorkerAsync(foobar);
}
POCOClassFoo foo = new POCOClassFoo();
void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//This automagically sets the UI to the data.
Foo.DataContext = foo;
}
void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//DOING THE HEAVY LIFTING HERE!
foo = parseanddownloadresult()!
}
Calling CancelAsync will still fire the RunWorkerCompleted event. In this event, you need to make sure that CancelAsync has not been called, by checking e.Cancelled. Until this event fires, you cannot call RunWorkerAsync.
Alternatively, I would recommend you do what Tigran suggested and create a new BackgroundWorker each time.
Further more, I would recommend storing the results of_backgroundWorker_DoWork in e.Result, then retrieve them from the same in _backgroundWorker_RunWorkerCompleted
Maybe something like this
BackgroundWorker _backgroundWorker;
private BackgroundWorker CreateBackgroundWorker()
{
var bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.DoWork += _backgroundWorker_DoWork;
bw.RunWorkerCompleted += new _backgroundWorker_RunWorkerCompleted;
return bw.
}
private void LoadHtmlAndParse(string foobar)
{
//Cancel whatever it is you're doing!
if (_backgroundWorer != null)
{
_backgroundWorker.CancelAsync();
}
_backgroundWorker = CreateBackgroundWorker();
//And start doing this immediately!
_backgroundWorker.RunWorkerAsync(foobar);
}
//you no longer need this because the value is being stored in e.Result
//POCOClassFoo foo = new POCOClassFoo();
private void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
//Error handling goes here.
}
else
{
if (e.Cancelled)
{
//handle cancels here.
}
{
//This automagically sets the UI to the data.
Foo.DataContext = (POCOClassFoo)e.Result;
}
}
private void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//DOING THE HEAVY LIFTING HERE!
e.Result = parseanddownloadresult()!
}
The thing is that CancelAsync() does what it climes: cancel in async way. That means that it will not stop immediately, but after some time. That time can never be calculated or predicted, so you have a couple of options:
Wait until this backround worker stops really, by waiting in cycle until IsBusy property of it becomes false
Or, I think, better solution is to start another background worker, considering that request of cancelation was already sent to the first one, so it will be soon or later stop. In this case, you need to know from which background worker data comes, in order to process it or not, cause on start of second the first one will still run and pump the data from WebService.
Hope this helps.
CancelAsync returns before the worker cancels and stops its work. Hence, your RunWorkerAsync call is starting before the worker is ready, and you're getting that error. You'll need to wait for the worker to be ready first.
When I'm not interested in tracking progress of an async operation, I tend to prefer to just slap a lambda at ThreadPool.QueueUserWorkItem instead of instantiating and setting up a background worker that I have to check the state of to be able to reuse in a sane way.
You need to verify before you kicks in.
f( !bw.IsBusy )
bw.RunWorkerAsync();
else
MessageBox.Show("Can't run the bw twice!");
You are calling CancelAsync without waiting for the background worker to actually cancel the work. Also you must have your own logic for cancelling the work. There is a good example on MSDN which shows how to do it. Basically in your parseanddownloadresult() method you need to check the CancellationPending property.
I'm very new to multithreading and lack experience. I need to compute some data in a different thread so the UI doesn't hang up, and then send the data as it is processed to a table on the main form. So, basically, the user can work with the data that is already computed, while other data is still being processed. What is the best way to achieve this? I would also be very grateful for any examples. Thanks in advance.
If you don't want to use the Background worker as answered by KMan you can create a thread yourself.
private void startJob(object work) {
Thread t = new Thread(
new System.Threading.ParameterizedThreadStart(methodToCall)
);
t.IsBackground = true; // if you set this, it will exit if all main threads exit.
t.Start(work); // this launches the methodToCall in its own thread.
}
private void methodToCall(object work) {
// do the stuff you want to do
updateGUI(result);
}
private void updateGUI(object result) {
if (InvokeRequired) {
// C# doesn't like cross thread GUI operations, so queue it to the GUI thread
Invoke(new Action<object>(updateGUI), result);
return;
}
// now we are "back" in the GUI operational thread.
// update any controls you like.
}
Checkout this BackgroundWorker sample document.
Initialise your background worker object
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
// I need to compute some data in a different thread so the UI doesn't hang up
// Well! ompute some data here.
bw.ReportProgress(percentOfCompletion, yourData) // and then send the data as it is processed
// percentOfCompletion-int, yourData-object(ie, you can send anything. it will be boxed)
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// to a table on the main form. So, basically, the user can work with the data that is already computed, while other data is still being processed
List<string> yourData = e.UserState as List<string>; // just for eg i've used a List.
}
What is the best way to achieve this?
RunWorkerAsync(); //This will trigger the DoWork() method
Use registry key to share the data between the threads
You can send the data to static variable, static variables are shared across threads.
i want to use a background thread for the process of loading the XML data, possibly with a progress bar to let the user know that the application is actively doing something.
i have written this code through searching the net.
i want to load a XML tree in treeview on winform when a user cliks a Browse button.
In case of a large XML file the winform freezes.So to let the user know that in background the work is going on i want to add a progress bar.i have used a background worker here.
But it is raising an exception of System.ArgumentException showing this message "The URL cannot be empty.\r\nParameter name: url" on xmlDocument.Load(txtFileName.Text); this line.
My xml file is in correct format and is at the proper location where i selected.
But i am unable to find the cause of this exception.
Can you please help out or tell me the correction in my code?
Thanks....
private void btnBrowse_Click(object sender,EventArgs e)
{
bgWorker1.RunWorkerAsync();
StripProgressBar.Value = 0;
toolStripStatusLabel1.Text = "Browsing for a Xml file";
if (open.ShowDialog(this) == DialogResult.OK)
{
txtFileName.Text = open.FileName;
initiatingTree(open.FileName); //this variable gives the name of selected file
}
while (this.bgWorker1.IsBusy)
{
StripProgressBar.Increment(1);
// Keep UI messages moving, so the form remains
// responsive during the asynchronous operation.
Application.DoEvents();
}
}//Browse button
private void bgWorker1_DoWork(object sender, DoWorkEventArgs e)
{
xmlDocument = new XmlDocument();
Thread.Sleep(5000);
xmlDocument.Load(txtFileName.Text);
btnBrowse.Enabled = false;
}
private void bgworker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Set progress bar to 100% in case it's not already there.
StripProgressBar.Value = 100;
if (e.Error == null)
{
MessageBox.Show(xmlDocument.InnerXml, "Download Complete");
}
else
{
MessageBox.Show("Failed to download file");
}
// Enable the Browse button and reset the progress bar.
this.btnBrowse.Enabled = true;
StripProgressBar.Value = 0;
toolStripStatusLabel1.Text = "work finished processing request.";
}//workerCompleted
You're starting the asynchronous process immediately when the user clicks "Browse", by calling
bgWorker1.RunWorkerAsync();
This calls the DoWork method of your background worker, which sleeps for 5 seconds, and pulls the value from txtFileName.Text whether or not the user has completed their entry in the FileOpenDialog.
You'd be better off moving the byWorker1.RunWorkerAsync() (and the busy waiting) into the if (open.ShowDialog(this) == DialogResult.OK) block.
private void btnBrowse_Click(object sender,EventArgs e)
{
StripProgressBar.Value = 0;
toolStripStatusLabel1.Text = "Browsing for a Xml file";
if (open.ShowDialog(this) == DialogResult.OK)
{
txtFileName.Text = open.FileName;
initiatingTree(open.FileName);
bgWorker1.RunWorkerAsync();
while (this.bgWorker1.IsBusy)
{
StripProgressBar.Increment(1);
// Keep UI messages moving, so the form remains
// responsive during the asynchronous operation.
Application.DoEvents();
}
}
}
For these kinds of problems, it can be helpful to put a breakpoint right where the file is going to get loaded, and see what the value is when that happens... you might notice that it's getting called with an empty string.
You might also consider the version of RunWorkerAsync that takes a parameter; you could pass the file in that way, instead of trying to read it asynchronously from the textbox.
And personally, I wouldn't use a loop that calls Application.DoEvents(); instead I'd return control back to the UI thread and then Invoke() onto it from the asynchronous thread to effect the progressbar updates.
When the method bgWorker1.RunWorkerAsync(); is called the event DoWork is fired.
Because the method is called in the beginning of the application, the file name text box is empty.
I hope you've understood.