I have made a simple gui in C# wpf, (sorry I can't show the GUI because my reputation is below 10)
It's consist of richtextbox and some other controls. Umm... this application will read a file then display the file contents to richtextbox line by line while reading the file using background worker. The function that read the file is like this :
public int parse_persoFile2(string fname, BackgroundWorker worker, DoWorkEventArgs e)
{
if (fname == null) return -1;
System.IO.StreamReader ifs;
ifs = new System.IO.StreamReader(fname);
int max = (int)e.Argument;
int p = 0;
while (ifs.Peek() != -1)
{
string tempData = ifs.ReadLine();
if (tempData.Contains("CMD=5107") || tempData.Contains("CMD=5106") || tempData.Contains("CMD=5102"))
{
//field.AppendText(tempData.Remove(tempData.LastIndexOf('\\')).Remove(0, 4) + "\r\n");
//field.AppendText("--------\r\n");
//System.Threading.Thread.Sleep(500);
string data = tempData.Remove(tempData.LastIndexOf('\\')).Remove(0, 4) + "\r\n";
worker.ReportProgress(p, data);
}
p++;
}
worker.ReportProgress(100);
return 0;
}
As we can see, I'm using backgroundworker in this function to get the string readed from file then send that string to reportprogress in order to be displayed in richtextbox. As a note that persoFile2 function is made from another object in my program... :-)
Then for the rest, I have made the doWork function, worker_progressChanged, and worker_RunWorkerCompleted to make backroundWorker works correctly. Those codes are like this :
private void doWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker wrk = sender as BackgroundWorker;
parser.parse_persoFile2(fileName, wrk, e);
}
private void proggChanged(object sender, ProgressChangedEventArgs e)
{
if(e.UserState != null)
mRTB.AppendText(e.UserState.ToString());
}
private void completed(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Ok....");
}
Umm.... When I run this program, it looks that my richtextbox is not prints the string line by line from the file, but it prints it just once at the end... :-3, .. Nah that's my real problem here. I have read this article http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx, but still have no idea.... :3
If you call the ReportProgress method too rapidly, it's possible that the UI thread will not have a chance to process the "progress" and update appropriately before the BackgroundWorker is hitting it again.
private void doWork(object sender, DoWorkEventArgs e)
{
var wrk = sender as BackgroundWorker;
// obviously you wouldn't really do this :)
while(true)
wrk.ReportProgress(0);
}
To see the effect you're looking expecting, you could set an artificial "pause" in your DoWork event, in order to give the UI time to update appropriately:
private void doWork(object sender, DoWorkEventArgs e)
{
var wrk = sender as BackgroundWorker;
var p = 0;
while(true)
{
wrk.ReportProgress(p++);
Thread.Sleep(100);
}
}
As for your situation, if the code is executing that quickly, you may not actually need to be executing it in a separate thread.
Alternatively, you could update your UI to say "Please wait. Loading...", then do everything you need to do in the BackgroundWorker, and just return the final result back to the UI at the end.
Related
I try to perform an easy task in an other backgroundthread, so the UI doesn't get blocked, but it still gets blocked. Did I forget anything?
public partial class backgroundWorkerForm : Form
{
public backgroundWorkerForm()
{
InitializeComponent();
}
private void doWorkButton_Click(object sender, EventArgs e)
{
if (backgroundWorker.IsBusy != true)
{
// Start the asynchronous operation.
backgroundWorker.RunWorkerAsync();
}
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//BackgroundWorker worker = sender as BackgroundWorker;
if (textBoxOutput.InvokeRequired)
{
textBoxOutput.Invoke(new MethodInvoker(delegate
{
for (int i = 0; i < 10000; i++)
{
textBoxOutput.AppendText(i + Environment.NewLine);
}
}));
}
}
}
While the textBox gets filled, the UI is blocked:
Your app wants to repeatedly send updates from the background thread to the UI. There is a built-in mechanism for this: the ProgressChanged event for the background worker. A ReportProgress call is triggered in the background, but executes on the UI thread.
I do change one thing, however. Performance can degrade with too many cross-thread calls. So instead of sending an update every iteration, I instead will batch them into 100.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
const int maxIterations = 10000;
var progressLimit = 100;
var staging = new List<int>();
for (int i = 0; i < maxIterations; i++)
{
staging.Add(i);
if (staging.Count % progressLimit == 0)
{
// Only send a COPY of the staging list because we
// may continue to modify staging inside this loop.
// There are many ways to do this. Below is just one way.
backgroundWorker1.ReportProgress(staging.Count, staging.ToArray());
staging.Clear();
}
}
// Flush last bit in staging.
if (staging.Count > 0)
{
// We are done with staging here so we can pass it as is.
backgroundWorker1.ReportProgress(staging.Count, staging);
}
}
// The ProgressChanged event is triggered in the background thread
// but actually executes in the UI thread.
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.ProgressPercentage == 0) return;
// We don't care if an array or a list was passed.
var updatedIndices = e.UserState as IEnumerable<int>;
var sb = new StringBuilder();
foreach (var index in updatedIndices)
{
sb.Append(index.ToString() + Environment.NewLine);
}
textBoxOutput.Text += sb.ToString();
}
EDIT:
This requires you set the background worker's WorkerReportsProgress property to true.
It's not important that you pass a count with the ReportProgress call. I do so just to have something and to quickly check if I can return.
One really should keep in mind about how many events are being invoked and queued up. Your original app had 10,000 cross thread invocations and 10,000 changed text events for textBoxOutput. My example uses 100 cross thread calls since I use a page size of 100. I could still have generated 10,000 changed text events for the textbox, but instead use a StringBuilder object to hold a full page of changes and then update the textbox once for that page. That way the textbox only has 100 update events.
EDIT 2
Whether or not your app needs paging is not the main deal. The biggest take away should be that the background worker really should use ReportProgress when trying to communicate info back to the UI. See this MSDN Link. Of particular note is this:
You must be careful not to manipulate any user-interface objects in
your DoWork event handler. Instead, communicate to the user interface
through the ProgressChanged and RunWorkerCompleted events.
Your invocation code should be outside the loop. Everything in the invoked codeblock, will be executed on the UI thread, thus blocking it.
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 0; i < 10000; i++)
{
// do long-running task
//if (textBoxOutput.InvokeRequired)
//{
textBoxOutput.Invoke(new MethodInvoker(delegate
{
textBoxOutput.AppendText(i + Environment.NewLine);
}));
//}
}
}
an easier way would be to do completely create your output text, and then paste the full output into the TextBox, then you only need one invocation
protected delegate void SetTextDelegate(TextBox tb, string Text);
protected void SetText(TextBox tb, string Text)
{
if (tb.InvokeRequired) {
tb.Invoke(new SetTextDelegate(SetText), tb, Text);
return;
}
tb.Text = Text;
}
and then inside your dowork
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
StringBuilder sb = new StringBuilder();
//BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 0; i < 10000; i++)
{
sb.AppendLine(i.ToString());
}
SetText(textBoxOutput, sb.ToString());
}
The name of the question is: "Updating the GUI from background worker", but the correct name world be: "Updating the GUI from background worker OR reporting multiple-variables (other than an integer) from background worker"
Please let me explain my situation. In a program I have a background worker which analyses the information.As the result of this analysis - form GUI elements should be populated with necessary data. In GUI I would like to update
2 datagridviews
1 listbox
5 labels
As I understand - I can only natively report 1 int value via ReportProgress() method of background worker.
So the question is - how can I pass a List<> ( + some other variables: string, int) via ReportProgress()? Basically - i want to update the GUI with the information but "1 integer" just won't do.. So either it should be possible to pass multiple variables via an ReportProgress() OR i can use an Invoke from inside the BackgroundWorker itself to update the GUI.. Personally I don't like the Invoke approach... What's your opinion?
Here is my code (see the comments):
private void button9_Click(object sender, EventArgs e) // start BW
{
bw.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
bw.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.RunWorkerAsync(10);
}
private void button10_Click(object sender, EventArgs e) // cancel BW
{
bw.CancelAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int count = (int)e.Argument;
for (int i = 1; i <= count; i++)
{
if (bw.CancellationPending)
{
e.Cancel = true;
break;
}
List<List<string>> list_result = new List<List<string>>();
list_result = Proccess();
bw.ReportProgress(list_result.Count()); // right now I can only return a single INT
/////////// UPDATE GUI //////////////
// change datagridview 1 based on "list_result" values
// change datagridview 2
// change listbox
// change label 1
// change label ..
Thread.Sleep(20000);
}
MessageBox.Show("Complete!");
e.Result = sum;
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
prog_count++;
listBox1.Items.Add("Count: (" + prog_count.ToString() + "/20). Found: " + e.ProgressPercentage.ToString() + ".");
}
There's a UserState parameter when calling ReportProgress.
var list_result = new List<List<string>>();
new backgroundWorker1.ReportProgress(0, list_result);
The parameter type is an object so you'll have to cast it back to the type you need:
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var userState = (List<List<string>>)e.UserState;
}
The tricky issue with this is, how do you determine whether you're passing back a List, or a list of lists, or a single string, number, etc. You'll have to test for each possibility in the ProgressChanged event.
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var myList = e.UserState as List<List<string>>;
if (myList != null)
{
// use list
return;
}
int myNumber;
if (Int32.TryParse(e.UserState.ToString(), out myNumber))
{
// use number
return;
}
var myString = e.UserState.ToString();
// use string
}
Alternatively, you could create a class that holds all the values you need (or use Tuple), run everything in the background to populate that class, then pass that to the RunWorkerCompleted event, and update your UI all at once from there.
I have written two very easy methods that enable you to invoke your code (only if required) and you only need to write your code once. I think this makes Invoke much friendlier to use:
1) BeginInvoke
public static void SafeBeginInvoke(System.Windows.Forms.Control control, System.Action action)
{
if (control.InvokeRequired)
control.BeginInvoke(new System.Windows.Forms.MethodInvoker(() => { action(); }));
else
action();
}
2) Invoke
public static void SafeInvoke(System.Windows.Forms.Control control, System.Action action)
{
if (control.InvokeRequired)
control.Invoke(new System.Windows.Forms.MethodInvoker(() => { action(); }));
else
action();
}
It can be called like this:
SafeInvoke(textbox, () => { textbox.Text = "text got changed"; });
Alternatively you could just
System.Windows.Forms.Form.CheckForIllegalCrossThreadCalls = false;
(which only changes behaviour in debug mode btw) and look if you run into problems.More often than not you actually don't. It took me quite some time to find cases very Invoke is really required for things not to get messed up.
The basic pattern for updating the UI from another thread is:
If controlItem.InvokeRequired Then
controlItem.Invoke(Sub() controlItem.Text = textUpdateValue)
Else
controlItem.Text = textUpdateValue
End If
This could update your list of controls without requiring you to pass anything through ReportProgress. If you would like to update your control from within the thread, I don't believe you need to check InvokeRequired, because it will always be required. However, best practices might be to expose the setting of a control via a property and then to do the full check so you can call it from anywhere.
private void timer1_Tick(object sender, EventArgs e)
{
string[] DF_Engines = { Form2.Engine1, Form2.Engine2, Form2.Engine3, Form2.Engine4, Form2.Engine5, Form2.Engine6 };
foreach (string DF_Engine in DF_Engines)
{
if (Convert.ToDouble(DF_Engine) != 99)
{
string Hex_ADD1 = "{" + DF_Engine + "|";
Console.WriteLine(Hex_ADD1);
serialPort1.Write(Hex_ADD1);
n = Convert.ToInt16(DF_Engine);
}
}
}
Form2.Engine1, Form2.Engine2....... are the values come from Form2 when the corresponding checkbox is selected. these would be 1, 2 , 3 so on....
My code send 01 when checkbox1 is selected but it send 01,02 without any delay when checkbox1 and checkbox2 are selected in Form2.
I need delay when asking 01 and 02 and so on as per my interest.
how could i do it convineintly
and when i use Thread.Sleep(500), the application gets slow.
need guidance.
Do not use Thread.Sleep on UI thread. It will definitely slow down your application. Rather start a new thread, pass data to that thread and let that thread send input to your application with delay.
System.Threading.Thread thread = new System.Threading.Thread((inputList) =>
{
foreach (var input in inputList as IEnumerable<int>)
{
//Send input
System.Threading.Thread.Sleep(500);
}
});
thread.Start();
Where inputList is an array of data(1,2,etc depending upon your checkbox selected).
You can consider using a separate thread of just a BackgroundWorker control. This way, you could use Thread.Sleep() method and it should not make the application's GUI unresponsive. You can find some more information about it on msdn: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx.
Just add a BackgroundWorker control to your form and use this code to handle appropriate events:
private void timer1_Tick(object sender, EventArgs e)
{
string[] DF_Engines = { Form2.Engine1, Form2.Engine2, Form2.Engine3, Form2.Engine4, Form2.Engine5, Form2.Engine6 };
//disable timer1, so it wont tick again until the current work is finished
timer1.Stop();
//start processing asynchronously, so GUI is still responsive
sampleBackgroundWorker.RunWorkerAsync(DF_Engines);
}
//this method should be attached to DoWork event of the BackgroundWorker
private void sampleBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
string[] DF_Engines = (e.Argument as string[]);
foreach (string DF_Engine in DF_Engines)
{
if (Convert.ToDouble(DF_Engine) != 99)
{
string Hex_ADD1 = "{" + DF_Engine + "|";
Console.WriteLine(Hex_ADD1);
serialPort1.Write(Hex_ADD1);
//n gets overwritten in each iteration, is this line required?
n = Convert.ToInt16(DF_Engine);
Thread.Sleep(500);
}
}
//you can also pass a result from this method back to the GUI thread like this
//e.Result = "job done";
//this can be read later in RunWorkerCompleted method of the BackgroundWorker
}
//this method should be attached to RunWorkerCompleted event of the BackgroundWorker
private void sampleBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//start timer1, so it can invoke the worker again
timer1.Start();
}
If you need more help with this, let me know.
I have a WPF application that executes external programs to process media files, and so that the GUI doesn't freeze when the media files are being processed, I execute the process on a separate thread through backgroundworker.
private void BackgroundWorkerExecProcess(Process process)
{
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = false;
worker.DoWork += DoWork;
worker.RunWorkerCompleted += WorkerCompleted;
worker.RunWorkerAsync(process);
}
void DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
Process process = e.Argument as Process;
process.Start();
string stderr = process.StandardError.ReadToEnd();
//I want to display stderr on main thread
process.WaitForExit();
}
void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//some code to update gui telling user that process has finished
}
so, if there is something printed to stderr, I can see it in the debugger, but if I try to do anything with the string stderr, such as if I have a textbox called "_tbLog" and did
_tbLog.Text+=stderr;
I get an error from the compiler about them being on separate threads. is there a way to pass the object from the worker thread to the main thread?
In DoWork, set e.Result to your object. In the WorkerCompleted you can get that object back out... it will once again be e.Result of type object. Just cast it to the object it was. The WorkerCompleted should be on the correct thread.
Here is one of mine:
private void workerUpdateBuildHistory_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
UpdateStatusModel model = (UpdateStatusModel)e.Argument;
BuildService buildService = new BuildService(model.TFSUrl);
e.Result = buildService.UpdateBuildHistoryList(model);
}
private void workerUpdateBuildHistory_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
BuildHistoryListModel model = (BuildHistoryListModel)e.Result;
if (model != null)
{
listViewPastBuilds.Items.Clear();
foreach (var item in model.Builds)
{
listViewPastBuilds.Items.Add(item);
}
}
}
Use your WorkerCompleted event handler to make changes the UI, it runs on the right thread. All you have to do is pass the string to the event handler. Which is what DoWorkEventArgs.Result was designed to do. You'll retrieve it in the event handler from e.Result. Thus:
void DoWork(object sender, DoWorkEventArgs e)
{
//...
e.Result = stderr;
}
void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null) DisplayError(e.Error);
else _tbLog.Text += (string)e.Result;
}
First you need to place whatever result object (in this example, a list of strings) in the DoWorkEventArgs.Result property, then retrieve this object via the RunWorkerCompletedArgs.Result property
Then, hook up an event handler RunWorkedCompleted event of the Background worker and have it pass back whatever object you want in the RunWorkerCompletedEventArgs.Result property.
Example:
void DoWork(object sender, DoWorkEventArgs arg)
{
List<string> results = new List<string>();
results.Add("one");
results.Add("two");
results.Add("three");
arg.Results = results;
}
void WorkComplete(object sender, runWorkerCompelteEventArgs arg)
{
//Get our result back as a list of strings
List<string> results = (List<string>)arg.Result;
PrintResults(results);
}
Note: I have not tested this code, but I believe it should compile.
http://msdn.microsoft.com/en-us/library/system.componentmodel.runworkercompletedeventargs.result.aspx
http://msdn.microsoft.com/en-us/library/system.componentmodel.doworkeventargs.aspx
you can also use the dispatcher as #Zembi mentiones:
this.Dispatcher.Invoke( new Action( () => {
_tbLog.Text+=stderr;
} ) );
you can also use TPL to make sure things get run on the right thread
-edit-
Here is a good article on diffrent ways to do ui updates, including using TPL
I have a background worker running a long database task. i want to show the progress bar while the task is running. Somehow the background worker won't report the progress of the task.
This is what i have:
BackgroundWorker _bgwLoadClients;
_bgwLoadClients = new BackgroundWorker();
_bgwLoadClients.WorkerReportsProgress = true;
_bgwLoadClients.DoWork += new DoWorkEventHandler(_bgwLoadClients_DoWork);
_bgwLoadClients.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_bgwLoadClients_RunWorkerCompleted);
_bgwLoadClients.ProgressChanged += new ProgressChangedEventHandler(_bgwLoadClients_ProgressChanged);
_bgwLoadClients.RunWorkerAsync(parms);
private void _bgwLoadClients_DoWork(object sender, DoWorkEventArgs e)
{
DataTable dt = getdate();
e.Result = dt;
}
void _bgwLoadClients_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
I am doing this in WPF, but i guess it won't make a difference.
Thanks in advance
You need to break your DoWork method down into reportable progress and then call ReportProgress.
Take for example the following:
private void Something_DoWork(object sender, DoWorkEventArgs e)
{
// If possible, establish how much there is to do
int totalSteps = EstablishWorkload();
for ( int i=0; i<totalSteps; i++)
{
// Do something...
// Report progress, hint: sender is your worker
(sender as BackgroundWorker).ReportProgress((int)(100/totalSteps)*i, null);
}
}
If your work can't be predetermined, try adding your own percentages:
private void Something_DoWork(object sender, DoWorkEventArgs e)
{
// some work
(sender as BackgroundWorker).ReportProgress(25, null);
// some work
(sender as BackgroundWorker).ReportProgress(50, null);
// some work
(sender as BackgroundWorker).ReportProgress(60, null);
// some work
(sender as BackgroundWorker).ReportProgress(99, null);
}
Modify the WorkReportProgress property of the backgroundworker object to true either in the properties window or in code.
You have to manualy call ReportProgress() to raise the ProgressChanged event.
You need to call worker.ReportProgress(percentComplete) in your DoWork method.
percentComplete should be computed based on the total work. For example:
for(int i =0; i != 100; i++) {
// do something
worker.ReportProgress(i);
}
Sometimes it is difficult to partition a job in several chunks to be possible to report the progress. Unfortunately the BackgroundWorker does not solve this, you have to do it yourself.
just report progress on dowork event
private void _bgwLoadClients_DoWork(object sender, DoWorkEventArgs e) {
int progresValue0to100 = 75;
(sender as System.ComponentModel.BackgroundWorker).ReportProgress(progresValue0to100);
//do your jobs..
}
it works like this
Progress must be sent from within the DoWork event by calling the ReportProgress method on the BackgroundWorker. In your case, you can't report any progress because all of the work is being done outside of the DoWork function. You can only report progress before and after the call to getdate(), but not during the call since the BackgroundWorker thread is busy.