Report Progress from a class with a backgroundWorker
-
Ok so I seem to have my backgroundWorkers worked out. However I can't figure out how to report progress back to the original form. I'll try to pice together the code to have this make sense. FYI I don't have anything in the progressUpdated even yet.
// This is the main form (obviously)
namespace MailTest
{
public partial class Form1 : Form
{
List<BGW> BGWs = new List<BGW>();
public Form1()
{
InitializeComponent();for (int i = 0; i < 3; i++) { BGWs.Add(new BGW()); BGWs\[i\].ProgressChanged += BGW\_ProgressChanged; BGWs\[i\].RunWorkerCompleted += BGW\_WorkerCompleted; } btnGetMessageInfo.Enabled = false; //btnCancelConnection.Enabled = false; } void BGW\_ProgressChanged(object sender, ProgressChangedEventArgs e) { BGW bgw = (BGW)sender; } void BGW\_WorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { BGW bgw = (BGW)sender; } class BGW : BackgroundWorker { public BGW() { this.WorkerReportsProgress = true; this.WorkerSupportsCancellation = true; } protected override void OnDoWork(DoWorkEventArgs e) { base.OnDoWork(e); WriteMail.WriteAndParseMail(); } }
// Further down
private void btnGetMessageInfo_Click(object sender, EventArgs e)
{
processNumberOfWorkers.howMayWorkers();
MessageBox.Show("There are " + GlobalVars.intHowManyWorkers + " workers needed!");
btnGetMessageInfo.Enabled = false;
backgroundWorker2.RunWorkerAsync();
//backgroundWorker1.RunWorkerAsync();
BGWs[0].RunWorkerAsync();
}// The class being called from the worker
class WriteMail
{
public static void WriteAndParseMail()
{
int intEmail;
bool bolSrvMsg;
//Start of Test Connection
// create a TCP Client for a TCP Connection
string txtLogString = null;TcpClient tcpClient = new TcpClient();
// further down where the report progress is
if (percentComplete <= highestPercentageReached)
{
if (percentComplete == 0) -
Ok so I seem to have my backgroundWorkers worked out. However I can't figure out how to report progress back to the original form. I'll try to pice together the code to have this make sense. FYI I don't have anything in the progressUpdated even yet.
// This is the main form (obviously)
namespace MailTest
{
public partial class Form1 : Form
{
List<BGW> BGWs = new List<BGW>();
public Form1()
{
InitializeComponent();for (int i = 0; i < 3; i++) { BGWs.Add(new BGW()); BGWs\[i\].ProgressChanged += BGW\_ProgressChanged; BGWs\[i\].RunWorkerCompleted += BGW\_WorkerCompleted; } btnGetMessageInfo.Enabled = false; //btnCancelConnection.Enabled = false; } void BGW\_ProgressChanged(object sender, ProgressChangedEventArgs e) { BGW bgw = (BGW)sender; } void BGW\_WorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { BGW bgw = (BGW)sender; } class BGW : BackgroundWorker { public BGW() { this.WorkerReportsProgress = true; this.WorkerSupportsCancellation = true; } protected override void OnDoWork(DoWorkEventArgs e) { base.OnDoWork(e); WriteMail.WriteAndParseMail(); } }
// Further down
private void btnGetMessageInfo_Click(object sender, EventArgs e)
{
processNumberOfWorkers.howMayWorkers();
MessageBox.Show("There are " + GlobalVars.intHowManyWorkers + " workers needed!");
btnGetMessageInfo.Enabled = false;
backgroundWorker2.RunWorkerAsync();
//backgroundWorker1.RunWorkerAsync();
BGWs[0].RunWorkerAsync();
}// The class being called from the worker
class WriteMail
{
public static void WriteAndParseMail()
{
int intEmail;
bool bolSrvMsg;
//Start of Test Connection
// create a TCP Client for a TCP Connection
string txtLogString = null;TcpClient tcpClient = new TcpClient();
// further down where the report progress is
if (percentComplete <= highestPercentageReached)
{
if (percentComplete == 0)MacRaider4 wrote:
I'm probably missing something obvious
For some obscure reason there is a
BackgroundWorker.WorkerReportsProgress
property, and in good Microsoft tradition its default value is the least useful one. :)Luc Pattyn [Forum Guidelines] [My Articles] Nil Volentibus Arduum
Please use <PRE> tags for code snippets, they preserve indentation, improve readability, and make me actually look at the code.
-
MacRaider4 wrote:
I'm probably missing something obvious
For some obscure reason there is a
BackgroundWorker.WorkerReportsProgress
property, and in good Microsoft tradition its default value is the least useful one. :)Luc Pattyn [Forum Guidelines] [My Articles] Nil Volentibus Arduum
Please use <PRE> tags for code snippets, they preserve indentation, improve readability, and make me actually look at the code.
I thought the only two choices for that are true and false? which I have in :
public BGW()
{
this.WorkerReportsProgress = true;
this.WorkerSupportsCancellation = true;
}is this where I get a lot of snickering behind my back :-D
-
Ok so I seem to have my backgroundWorkers worked out. However I can't figure out how to report progress back to the original form. I'll try to pice together the code to have this make sense. FYI I don't have anything in the progressUpdated even yet.
// This is the main form (obviously)
namespace MailTest
{
public partial class Form1 : Form
{
List<BGW> BGWs = new List<BGW>();
public Form1()
{
InitializeComponent();for (int i = 0; i < 3; i++) { BGWs.Add(new BGW()); BGWs\[i\].ProgressChanged += BGW\_ProgressChanged; BGWs\[i\].RunWorkerCompleted += BGW\_WorkerCompleted; } btnGetMessageInfo.Enabled = false; //btnCancelConnection.Enabled = false; } void BGW\_ProgressChanged(object sender, ProgressChangedEventArgs e) { BGW bgw = (BGW)sender; } void BGW\_WorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { BGW bgw = (BGW)sender; } class BGW : BackgroundWorker { public BGW() { this.WorkerReportsProgress = true; this.WorkerSupportsCancellation = true; } protected override void OnDoWork(DoWorkEventArgs e) { base.OnDoWork(e); WriteMail.WriteAndParseMail(); } }
// Further down
private void btnGetMessageInfo_Click(object sender, EventArgs e)
{
processNumberOfWorkers.howMayWorkers();
MessageBox.Show("There are " + GlobalVars.intHowManyWorkers + " workers needed!");
btnGetMessageInfo.Enabled = false;
backgroundWorker2.RunWorkerAsync();
//backgroundWorker1.RunWorkerAsync();
BGWs[0].RunWorkerAsync();
}// The class being called from the worker
class WriteMail
{
public static void WriteAndParseMail()
{
int intEmail;
bool bolSrvMsg;
//Start of Test Connection
// create a TCP Client for a TCP Connection
string txtLogString = null;TcpClient tcpClient = new TcpClient();
// further down where the report progress is
if (percentComplete <= highestPercentageReached)
{
if (percentComplete == 0)Easy... Your BGWs are defined in Form1, while you're trying to access them from within WriteMail. I would suggest adding a "sender" parameter to WriteAndParseMail(), so you can pass the worker into it.
protected override void OnDoWork(DoWorkEventArgs e)
{
base.OnDoWork(e);
WriteMail.WriteAndParseMail(this);
}And in the worker thread...
highestPercentageReached = percentComplete + 1;
string strCounter = currentMail + " of " + intEmail;
(sender as BackgroundWorker).ReportProgress(percentComplete, strCounter);Proud to have finally moved to the A-Ark. Which one are you in?
Author of the Guardians Saga (Sci-Fi/Fantasy novels) -
I thought the only two choices for that are true and false? which I have in :
public BGW()
{
this.WorkerReportsProgress = true;
this.WorkerSupportsCancellation = true;
}is this where I get a lot of snickering behind my back :-D
Sorry, I initially didn't read all your code, I just reported the most likely oversight. I looked through your code now, and I would say: 1. your ReportProgress method should get called, however it is empty. What makes you say it doesn't work? 2. your Mail stuff is static, having multiple BGW's active in WriteAndParseMail() is not safe, it requires data synchronization. 3. I'm not sure you can have multiple TcpClient instances operating concurrently like that. 4. your percentage stuff is incomplete, so I can't be sure it is correct. 5. for overall progress, I often use a timer (Windows.Forms.Timer) which just gathers the number and updates the GUI at fixed intervals, say once a second. That turns out to be easier than having to check for actual percentage changes (which is however more economical). :)
Luc Pattyn [Forum Guidelines] [My Articles] Nil Volentibus Arduum
Please use <PRE> tags for code snippets, they preserve indentation, improve readability, and make me actually look at the code.
-
Sorry, I initially didn't read all your code, I just reported the most likely oversight. I looked through your code now, and I would say: 1. your ReportProgress method should get called, however it is empty. What makes you say it doesn't work? 2. your Mail stuff is static, having multiple BGW's active in WriteAndParseMail() is not safe, it requires data synchronization. 3. I'm not sure you can have multiple TcpClient instances operating concurrently like that. 4. your percentage stuff is incomplete, so I can't be sure it is correct. 5. for overall progress, I often use a timer (Windows.Forms.Timer) which just gathers the number and updates the GUI at fixed intervals, say once a second. That turns out to be easier than having to check for actual percentage changes (which is however more economical). :)
Luc Pattyn [Forum Guidelines] [My Articles] Nil Volentibus Arduum
Please use <PRE> tags for code snippets, they preserve indentation, improve readability, and make me actually look at the code.
When I use the hard coded version I have:
private void backgroundWorker1\_ProgressChanged(object sender, ProgressChangedEventArgs e) { pgbWrite.Value = e.ProgressPercentage; lblCurrentCount.Text = e.UserState.ToString(); }
Of which does work (when it's not calling from the class), sorry I should have included that. What makes me say it doesn't work is when I uncomment that section of code out I get a error saying it's not in the current context. I have had 2 TcpClients running at the same time, so that doesn't seem to be a issue. Is this what you mean by percentage stuff being incomplete?
percentComplete = (int)((float)currentMail / (float)intEmail \* 100); if (percentComplete <= highestPercentageReached) { if (percentComplete == 0) { percentComplete = 1; } highestPercentageReached = percentComplete + 1; string strCounter = currentMail + " of " + intEmail; //backgroundWorker1.ReportProgress(percentComplete, strCounter); BGWs\[0\].ReportProgress(percentComplete, strCounter); }
-
Easy... Your BGWs are defined in Form1, while you're trying to access them from within WriteMail. I would suggest adding a "sender" parameter to WriteAndParseMail(), so you can pass the worker into it.
protected override void OnDoWork(DoWorkEventArgs e)
{
base.OnDoWork(e);
WriteMail.WriteAndParseMail(this);
}And in the worker thread...
highestPercentageReached = percentComplete + 1;
string strCounter = currentMail + " of " + intEmail;
(sender as BackgroundWorker).ReportProgress(percentComplete, strCounter);Proud to have finally moved to the A-Ark. Which one are you in?
Author of the Guardians Saga (Sci-Fi/Fantasy novels)I tried that but no luck, though I'm sure it's me. This was so much easier in VB...
-
I tried that but no luck, though I'm sure it's me. This was so much easier in VB...
Oh, ya know what... I wonder if the BackgroundWorker only allows ReportProgress to be called during the DoWork... Technically, your routine is running AFTER DoWork completes... I really don't think it's intended to be run that way. The standard way to use a BackgroundWorker is to hook the DoWork EVENT, and put your code there. No need to subclass it, unless you really want to centralize those two boolean property settings. Just guessing here, of course... I've never seen the BW subclassed... Never had any need to do that.
Proud to have finally moved to the A-Ark. Which one are you in?
Author of the Guardians Saga (Sci-Fi/Fantasy novels) -
Oh, ya know what... I wonder if the BackgroundWorker only allows ReportProgress to be called during the DoWork... Technically, your routine is running AFTER DoWork completes... I really don't think it's intended to be run that way. The standard way to use a BackgroundWorker is to hook the DoWork EVENT, and put your code there. No need to subclass it, unless you really want to centralize those two boolean property settings. Just guessing here, of course... I've never seen the BW subclassed... Never had any need to do that.
Proud to have finally moved to the A-Ark. Which one are you in?
Author of the Guardians Saga (Sci-Fi/Fantasy novels)Ok I've been doing some more reading, would this be better using a "thread" vs a BGW? Now granted I don't need to put this in a seperate class, however for what ever reason when I use
BGWs[0].RunWorkerAsync();
vsbackgroundWorker1.RunWorkerAsync();
I'm not able to have the "function" in the same class (the Form) thus why I moved it to a seperate class. Plus when I was getting into more advanced someone reccomended keeping as little as you can on your main form and using classes for as much as possible. Is this a good habbit, bad habbit or just personal preference? Where I work there are two of us, and he does all the C++ stuff and I do the VB (6+ years, though .net in only the last year) and just starting with C# (about 6 months ago). I have been able to subclass a BGW in VB but obviously not in C#, yet again if I don't have to not a big deal. Basically what I need to do is be able to run the same function up to 3 times concurrently. I believe if my memory serves me correctally each "occurance" of the function will have it's own local variables. I also believe that if I use a thread vs a bgw I can lock what I'm writing to and instead of throwing a error, the threads will wait until the current one is done updating. Lets say it's justlblOutput.Text = e.UserState.ToString();
(as if it were a bgw perhaps in a thread you can use the variable directally)? Does this help to explain what I'm trying to accomplish and maybe give you all a better idea of what path I should be taking? -
Oh, ya know what... I wonder if the BackgroundWorker only allows ReportProgress to be called during the DoWork... Technically, your routine is running AFTER DoWork completes... I really don't think it's intended to be run that way. The standard way to use a BackgroundWorker is to hook the DoWork EVENT, and put your code there. No need to subclass it, unless you really want to centralize those two boolean property settings. Just guessing here, of course... I've never seen the BW subclassed... Never had any need to do that.
Proud to have finally moved to the A-Ark. Which one are you in?
Author of the Guardians Saga (Sci-Fi/Fantasy novels)Ian Shlasko wrote:
I wonder if the BackgroundWorker only allows ReportProgress to be called during the DoWork
No, internally it uses a
SendOrPostCallback
delegate and anAsyncOperation
. If the AsyncOperation is null (there is no active worker thread) then the delegate is called directly. If it's not null then theAsyncOperation
'sPost
method is called with the delegate and args as parameters which automatically invokes it on the syncronization context thatRunWorkerAsync
existed in.public void ReportProgress(int percentProgress, object userState)
{
if (!this.WorkerReportsProgress)
{
throw new InvalidOperationException(SR.GetString("BackgroundWorker_WorkerDoesntReportProgress"));
}
ProgressChangedEventArgs arg = new ProgressChangedEventArgs(percentProgress, userState);
if (this.asyncOperation != null)
{
this.asyncOperation.Post(this.progressReporter, arg);
}
else
{
this.progressReporter(arg);
}
}Dave
Binging is like googling, it just feels dirtier. Please take your VB.NET out of our nice case sensitive forum. Astonish us. Be exceptional. (Pete O'Hanlon)
BTW, in software, hope and pray is not a viable strategy. (Luc Pattyn) -
Ok I've been doing some more reading, would this be better using a "thread" vs a BGW? Now granted I don't need to put this in a seperate class, however for what ever reason when I use
BGWs[0].RunWorkerAsync();
vsbackgroundWorker1.RunWorkerAsync();
I'm not able to have the "function" in the same class (the Form) thus why I moved it to a seperate class. Plus when I was getting into more advanced someone reccomended keeping as little as you can on your main form and using classes for as much as possible. Is this a good habbit, bad habbit or just personal preference? Where I work there are two of us, and he does all the C++ stuff and I do the VB (6+ years, though .net in only the last year) and just starting with C# (about 6 months ago). I have been able to subclass a BGW in VB but obviously not in C#, yet again if I don't have to not a big deal. Basically what I need to do is be able to run the same function up to 3 times concurrently. I believe if my memory serves me correctally each "occurance" of the function will have it's own local variables. I also believe that if I use a thread vs a bgw I can lock what I'm writing to and instead of throwing a error, the threads will wait until the current one is done updating. Lets say it's justlblOutput.Text = e.UserState.ToString();
(as if it were a bgw perhaps in a thread you can use the variable directally)? Does this help to explain what I'm trying to accomplish and maybe give you all a better idea of what path I should be taking?MacRaider4 wrote:
(as if it were a bgw perhaps in a thread you can use the variable directally)?
Nope, that limitation is universal to all types of multithreading in WinForms (And WPF, for that matter). No touching the GUI controls except from the GUI thread.
MacRaider4 wrote:
Plus when I was getting into more advanced someone reccomended keeping as little as you can on your main form and using classes for as much as possible. Is this a good habbit, bad habbit or just personal preference?
That's generally a good idea, except in really small tools when it would be complete overkill. The form is your connection to the GUI, and business logic goes elsewhere... In your particular situation, I think I would probably handle it something like this: (Consider this C#ish pseudocode, as I don't have VS installed on my home machine at the moment)
// This can go in a static class somewhere
public static void SendMessagesAsync(ProgressChangedHandler progressCallback, params SomeClass[] messages)
{
for (int idx = 0; idx < messages.Length; idx++)
{
BackgroundWorker wkr = new BackgroundWorker();
wkr.WorkerSupportsProgress = true;
wkr.ProgressChanged += progressCallback; // This will go back to your main form and display something
wkr.DoWorkEventArgs += wkr_DoWork; // This is inside the same static class, below, not part of its public interface
wkr.RunWorkerAsync(messages[idx]);
}
}// All of your workers can use the same DoWork, as long as they're given different arguments
private static void wkr_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker wkr = sender as BackgroundWorker; // Use this to report progress
SomeClass args = e.Argument as SomeClass; // Here's any info you need to pass to the worker// ... Do your processing here ... WriteMail, or whatever
}// And on your form...
private void SomethingCalledFromYourForm()
{
MyStaticClass.SendMessagesAsync(wkr_ProgressChanged, new SomeClass[] {
new SomeClass() { Whatever = parameters, YouWant = toset },
new SomeClass() { Subject = someone, Body = "something else?", Recipient = "someone@somewhere.sometime" }
});
}I don't know what you need to pass to the WriteMail function, but that's generally how you get information into a background worker... Make a class to hold it, and pass it as the argument to RunWorkerAsync().
Pro