Ok, I've been banging my head against this all day. I'm still fairly new to programming, and its quite possible that my entire approach here is misguided. But anyway...so I have a simple gui app with a listbox full of folders, and I'm executing an operation on every file in each folder sequentially. This is a very long operation, so I have two progress bars - one for each file, and one for each folder.
private void buttonApplySelected_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
double percentToIncrement = 100.0 / Convert.ToDouble(selectedDirList.Count);
double percentComplete = percentToIncrement;
folderProgressBar.Value = 0;
foreach (string dir in selectedDirList)
{
engine = new OEngine.OEngine(dir, backgroundWorker1);
engine.ProcessSelected(processType);
int percentCompleteInt = Convert.ToInt32(percentComplete);
folderProgressBar.Value = percentCompleteInt;
percentComplete += percentToIncrement;
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
fileProgressBar.Value = e.ProgressPercentage;
}
The BackgroundWorker itself is passed down to the engine, and it updates its progress within the code of the engine processing that folder. (This is probably my first mistake.) The UI catches that ProgressChanged event and updates the fileProgressBar in it's own thread.
But the folderProgressBar needs to be updated once every pass through the for loop, but it gives me Cross-thread operation not valid: Control 'folderProgressBar' accessed from a thread other than the thread it was created on.
If I move it out of the for loop, it wont update after each folder.
If I move all the UI updating out of the DoWork function, and call the DoWork function in a for loop instead, it obviously doesnt wait for each folder to finish and I get "the worker is still busy" exception.
Any ideas?
The general solution for windows form:
Use
WindowsFormsSynchronizationContext syncContext = new WindowsFormsSynchronizationContext();
...
//in the background work or any non UI Thread
//Trigger an update in the GUI thread; one can also use syncContext.Send (depending on the need for syncrhonous or async operation)
syncContext.Post(UpdateGUI, userData);
...
void UpdateGUI(object userData)
{
//update your progress bar
}
If using wpf, declare a variable syncContex.
SynchronizationContext syncContext;
//And when in the UI thread, set the variable's value as
syncContext = SynchronizationContext.Current;
//Then from the non-UI thread,
syncContext.Post(UpdateGUI, userData);
Related
I have a background worker with a long running task. The task goes through a list of files and I want to update the user with which file we are on. I have a tool strip that has a label named panel1.text. The progress bar is working however the label is not changing in my ProgressChanged method i.e. It should say Processing File1 then change to Processing File2, but it stays on the default of Processing.
private void btnProcess_Click(object sender, EventArgs e)
{
toolStripProgressBar1.Visible = true;
toolStripProgressBar1.Maximum = 1000000000;
panel1.Text = "Processing "; // this appears properly
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(processFiles);
worker.ProgressChanged += ProgressChanged;
worker.RunWorkerAsync();
while (worker.IsBusy)
{
// the reason for this is because nothing can happen until the processing is done
toolStripProgressBar1.Increment(1);
}
// more processing
}
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
panel1.Text = "Processing "+ e.UserState.ToString(); <<<---- This is Not Updating panel1.Text but it evaluates properly
}
private void processFiles(object sender, EventArgs e)
{
int retVal = 0;
foreach (string fileName in listBox1.Items)
{
ProgressChangedEventArgs ea = new ProgressChangedEventArgs(1,fileName);
ProgressChanged(this, ea);
// do more processing
}
}
I would appreciate any help.
You are using the same thread, which is being blocked by another process. You need to use a Task to create a new thread and possibly use Dispatcher.BeginIvoke if the control is on the other thread. Make sure whatever Button Click, etc is happening is marked with the Async keyword as well to make it Asynchronous.
Example:
Await Task mytask = Task.Run(() =>
for(var i = 0; i < 1000; i++)
{
Label.Dispatcher.BeginInvoke( () =>
UpdateMe(int i, LabelClass/Component class/component)});
Then inside the Label Class or wherever the label is:
Public void UpdateMe(int i, LabelClass class)
{
class.label.content = Cint((i/Total)*100);
Thread.Sleep(500);
}
There are other ways to do it as well such as Binding the value to the UI, but this will give you a better understanding of why its not working and how things work with other threads.
If you want to really get a visual understanding call:
`Console.WriteLine($"Current Thread ID: System.Threading.Thread.CurrentThread.ManagedThreadId}");`
Right before you go into the Task---it will give you the main thread ID
Then inside the Task call it again...this will give you the secondary thread ID.
Then Right before the Dispatcher call:
Console.WriteLine($"Do I have access to the label on this thread? {Label.Dispatcher.CheckAccess()}";
If you have access it will display True, if not it will display False...In your case it will display false because its owned by the other thread, but you can use the Dispatcher to be able to do work on that thread while in another thread...
Also, I recommend you not use Background Worker and use Tasks instead...this explains why in depth...basically Tasks do everything Background workers do and more, have less issues and are easier to work with...
http://blog.stephencleary.com/2013/09/taskrun-vs-backgroundworker-conclusion.html
As already commented by Ivan, remove the while loop while (worker.IsBusy) as it's blocking the UI thread to process further. As well, you should enable the WorkerReportsProgress to true
worker.WorkerReportsProgress = true;
worker.ProgressChanged += ProgressChanged;
while (!worker.IsBusy)
{
worker.RunWorkerAsync();
}
Per your comment, move those later processing to BackgroundWorker.RunWorkerCompleted Event
I am stuck on an issue where I am using Backgroundworker to show the progress of my work in a progress bar. Code used for backgroundworker:-
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(200);
for (int i = 0; i <= 100; i++)
{
Delegate del= new DELEGATE(simulateHeavyWork);
this.Invoke(del);
backgroundWorker1.ReportProgress(i);
if (backgroundWorker1.CancellationPending)
{
e.Cancel = true;
backgroundWorker1.ReportProgress(0);
return;
}
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
percentLabel.Text = e.ProgressPercentage.ToString() + "%";
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
MessageBox.Show("Cancelled");
}
else
{
MessageBox.Show("Completed");
}
}
I have created a delegate on the code:-
public partial class Form1 : Form
{
private delegate void DELEGATE();
public Form1()
{
InitializeComponent();
}
private void simulateHeavyWork()
{
Thread.Sleep(100);
...lines of code to perform some search logs operation..
....
}
}
The functionality I want to achieve is that progress bar should report the progress of my function simulateHeavyWork() which is actually using UI thread as it needs to take input from my form controls and update it as well.
Now the problem which is happening is that code is actually calling simulateHeavyWork() and gives the output that is updating ui controls and work is done. (Note: I have used delegate here to avoid error cross controls running on ui thread as my function needs to use UI controls.)
Once that is done, it starts updating progress bar and which is wrong and looks like it calls simulateHeavyWork again and again with the gap of sleep(100).
user3222101, as Andy stated before, you are running simulateHeavyWork() continuously. Moreover, by calling Invoke you are running this method in the UI thread which cause an extra sleep in the UI thread. Basically Invoke uses the message loop (pump) of the Control you use it on (Form1 in that case) and put your delegate to the queue of the UI thread in order to execute. This is not a good practice I think, due to the Sleep() call and time consuming log operations in your simulateHeavyWork() method.
I hope, understand you problem clearly.What I suggest is separation of the time consuming log operations from UI thread. Do not spend the valuable time of UI thread with slow and boring I/O operations. Get the values from the controls (using Invoke in the BackgroundWorker as I will explain below), do whatever you want in BackgroundWorker and update your GUI (again using Invoke) without touching the UI thread for this kind of heavy tasks.
As Andy suggested, you can pass data via the parameter of RunWorkerAsync and you should create a class which can store any data you need (because it accepts only one parameter). However, you can get the values from your Form whenever you need from another thread by using Invoke. Invoke
method also returns the value from your delegate (please see the example at the link below) and this gives you a chance to get the values of your controls on the form. Create a delegate which returns an object of type class that you crated for RunWorkerAsync and use this values in the BackgroundWorker thread. Please, have a look at the example in here.
public static string GetTextThreadSafe(this TextBox box)
{
return GetTextBoxText(box);
}
Also, example uses Func<...> in order to return value.
By this way you can sleep (in BackgroundWorker thread) for a while then get the values from your controls (current values) and do whatever you want (again in BackgroundWorker thread). I think, this improves your code.
From your question: "which is wrong and looks like it calls simulateHeavyWork again and again with the gap of sleep(100)."
Of course it calls. Just look at your code:
for (int i = 0; i <= 100; i++)
{
Delegate del= new DELEGATE(simulateHeavyWork);
this.Invoke(del);
So you are calling simulateHeavyWork 100 times here. And since you've typed Thread.Sleep(100); in the body of simulateHeavyWork - gap between calls is about Sleep(100)
I've been trying to learn more about asynchronous tasks and threading but not making a ton of headway.
I'm trying to load an "Engine" type of thread that will run in the background upon launch and be able to access the UI Thread to update variables, without hanging the UI Thread.
In the below code, Engine is called, and a Ticker object is created which holds the current value of (Litecoin/USD) called Last, also holds several other values that would be useful. This code successfully assigns the current value to label1.text. I don't necessarily need code but what approach would I take to create a ticker object in the background every second and update the UI thread with each new Ticker objects values.
Is this a good case for a background worker?
private void Form1_Load(object sender, EventArgs e)
{
Engine();
}
private void Engine()
{
Ticker ltcusd = BtceApi.GetTicker(BtcePair.LtcUsd);
label1.Text = "LTC/USD:" + ltcusd.Last;
}
EDIT:
If I do the following, label1 throws an InvalidOperationException due to a Cross-thread operation attempt (label1 in the UI thread).
private void Form1_Load(object sender, EventArgs e)
{
var t = Task.Factory.StartNew(() => Engine());
t.Start();
}
private void Engine()
{
while (true)
{
Thread.Sleep(1000);
Ticker ltcusd = BtceApi.GetTicker(BtcePair.LtcUsd);
label1.Text = "LTC/USD: " + ltcusd.Last;
}
}
Using async/await, the simplest way of getting an "asynchronous" sort of API is to invoke a new task. It's not great, but it'll make things simpler. I would probably create a new class which basically wrapped all the BtceApi methods in tasks:
public class BtceApiAsync
{
public Task<Ticker> GetTickerAsync(BtcePair pair)
{
return Task.Run(() => BtceApi.GetTicker(pair));
}
// etc
}
Then you can use a timer which fires once per second, which will start off a new task and update the UI appropriately:
// Keep a field of type System.Windows.Forms.Timer
timer = new Timer();
timer.Interval = 1000;
timer.Tick += DisplayTicker;
timer.Start();
...
private async void DisplayTicker(object sender, EventArgs e)
{
Ticker ticker = await BtceApiAsync.GetTickerAsync(BtcePair.LtcUsd);
label1.Text = "LTC/USD: " + ltcusd.Last;
}
Note that this doesn't mean the screen will be updated once per second... there will be a new task started once per second, and as soon as each task completes, the UI will be updated.
The use of await here - from an async method started on the UI thread - means you don't need to worry about using the UI; the whole async method will execute on the UI thread, even though the fetch itself happens in a different thread.
You can try ContinueWith to update the Label at the end of the task. If you want to update it event before the task ends then raise an event which is registered by on the UI thread. The event can then update the label.
I suppose this is Windows Forms. You could do it "old school style" and set the label text on the UI thread, and you can do that by passing delegate to the BeginInvoke or Invoke method.
private void Engine()
{
while (true)
{
Thread.Sleep(1000);
Ticker ltcusd = BtceApi.GetTicker(BtcePair.LtcUsd);
UpdateText("LTC/USD: " + ltcusd.Last);
}
}
private void UpdateText(string text)
{
//Inspect if the method is executing on background thread
if (InvokeRequired)
{
//we are on background thread, use BeginInvoke to pass delegate to the UI thread
BeginInvoke(new Action(()=>UpdateText(text)));
}
else
{
//we are on UI thread, it's ok to change UI
label1.Text = text;
}
}
I'm trying to run a method that gets file list from specified folders and represents it in DataGridView. The method is ran in BackgroundWorker, so I'm expecting GUI to stay active. But it still freezes. Here's an example:
private void startScan_Click(object sender, EventArgs e)
{
bckgrFileScanner.RunWorkerAsync();
}
private void bckgrFileScanner_DoWork(object sender, DoWorkEventArgs e)
{
//for each folder in list perform this function, which scans folder and gets all files
for (int i = 0; i < folderList.Items.Count; i++)
{
GetFileList(ref scannedFiles, folderList.Items[i].ToString(), bckgrFileScanner);
}
}
public static void GetFileList(ref List<FileInfo> fList, string fPath, BackgroundWorker scanner)
{
DirectoryInfo di = new DirectoryInfo(fPath);
FileInfo[] fi = di.GetFiles();
foreach (FileInfo fiTemp in fi)
{
if (fiTemp.Name.StartsWith("~$") == false)
{
//adds items to list of all scanned files
fList.Add(fiTemp);
//reports file name to ProgressChanged method
scanner.ReportProgress(0, fiTemp);
}
}
DirectoryInfo[] dFolders = di.GetDirectories();
//use recursion for all subfolders
foreach (DirectoryInfo d in dFolders)
{
GetFileList(ref fList, d.FullName, scanner);
}
}
private void bckgrFileScanner_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//userstate is filename, so add it to table
filesDataGrid.Rows.Add(e.UserState.ToString());
}
Do not update your reportprogress event at each row of your loop.
Instead, call the ReportProgress every 2 or more iteration (the best way is to compute et step to not update at each row if you have 200000)
And fill the grid when the whole process has finished (in the completed event), to update all the rows. I think progress is meant to update a progress only, not fill a bunch of data into a control but i may be wrong :
from here :
Tips You probably know more than you think about the BackgroundWorker
class. BackgroundWorker has a name that might indicate it is more
complex than it really is. There are many more details about threading
and abort calls, but once you understand that BackgroundWorker is just
a structural "overlay" to threads in Windows Forms, it is quite
intuitive. Here are the steps again:
First, call RunWorkerAsync with an argument. You can pass any argument
to this method on BackgroundWorker, including null. It simply must
inherit from object, which everything does.
Second, custom processing is run. Your expensive code is executed in
the DoWork method. Insert pause here as your program does its
calculations.
Third, it finishes. When your processing is done, RunWorkerCompleted
is called. In this method, you receive the result. In this way, your
BackgroundWorker object modifies an object on another thread, and you
receive it when it is done.
I think it hangs up when too much updates in a short time are required, and even calling Apllication.DoEvents() does not work all the times.
I hope it will help, a little late i know, but it's better than never ;)
This (simplified) example works for me and has a lovely responsive UI:
BackgroundWorker m_objWorker = new BackgroundWorker();
public FormBackgroundWorkerExample()
{
InitializeComponent();
m_objWorker.WorkerReportsProgress = true;
m_objWorker.DoWork += new DoWorkEventHandler(m_objWorker_DoWork);
m_objWorker.ProgressChanged += new ProgressChangedEventHandler(m_objWorker_ProgressChanged);
}
private void btnStart_Click(object sender, EventArgs e)
{
m_objWorker.RunWorkerAsync();
}
private void m_objWorker_DoWork(object sender, DoWorkEventArgs e)
{
//for each folder in list perform this function, which scans folder and gets all files
for (int i = 0; i <= 100; i++)
{
m_objWorker.ReportProgress(i, "FooBar");
Thread.Sleep(1000);
}
}
private void m_objWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
dataGridView1.Rows.Add(e.UserState.ToString());
}
Perhaps this'll give you an idea where yours is going wrong?
Edit: You might want to try it without the datagrid just to try and isolate the problem.
Might be because you are locking the backgroundworker itself.
In your DoWork method
GetFileList(ref scannedFiles, folderList.Items[i].ToString(), bckgrFileScanner);
Normaly you should have access to bckgrFileScanner from your DoWork Method, and just call it directly as bckgrFileScanner.ReportProgress( .... .... )
When passing it like you do, it will now report progress on the background workers thread, which is now not the same as the UI thread. (who owns bckgrFileScanner)
Edit To clarify:
Your UI thread owns bckgrFileScanner and fires RunWorkerAsync()
The stuff that happens in DoWork is now on its own thread.
The DoWork thread is "stealing" the variable bckgrFileScanner
Now the ReportProgress fires on the DoWork thread, instead of the UIs thread
You are calling ReportProgress on scanner. Shouldn't be that bckgrFileScanner?
EDIT
Is by any chance the scannedFiles list databound to the UI? If so a change to the list causes a UI update.
Possible reasons:
I think you just want to report the filename:
//reports file name to ProgressChanged method
scanner.ReportProgress(0, fiTemp.Name);
Or the folderList in DoWork is a UI control:
for (int i = 0; i < folderList.Items.Count; i++)
{
GetFileList(ref scannedFiles, folderList.Items[i].ToString(), bckgrFileScanner);
}
Why not pass the list of folders to the RunWorkerAsync method.
I have question about progressbar show value.
I have this main thread
private void button1_Click(object sender, EventArgs e)
{
progress prog = new progress();
progress.progressEvent += new progress.progressEventHandler(progressEvent);
for(int i=0;i<100;i++)
{
Thread.Sleep(100);
prog.incA();
}
}
void progressEvent(object sender)
{
if (progressBar1.InvokeRequired)
{
//Tady mi to caka az kym nedobehne cyklus for a pak zacne tohleto fungovat
progressBar1.Invoke(new ChangeProgressBarValue(ProgressStep));
}
else
{
ProgressStep();
}
}
public void ProgressStep()
{
progressBar1.PerformStep();
}
public class progress
{
private ThreadStart ts;
private Thread th;
private bool status = true;
public delegate void progressEventHandler(object sender);
public static event progressEventHandler progressEvent;
private int b,a = 0;
public progress()
{
ts=new ThreadStart(go);
th = new Thread(ts);
th.IsBackground = true;
th.Start();
}
public void incA()
{
a++;
if(a==100)
status = false;
}
private void go()
{
while (status)
{
if (a != b)
{
b = a;
if (progressEvent != null)
progressEvent(this);
}
}
th.Abort();
}
}
and my problem is IF start main thread and call IncA this method call event and in event is progressbar invoke
and this invoke waiting to end main thread FOR
why waiting?
thx
Your loop in the main thread is preventing "paint" events from happening. Since you are calling your progress bar's function from withing that thread, you will never see the updates.
You need to move the code to do the incrementing to another thread entirely.
Here is a sample of what you want to do using a Button, a BackgroundWorker, and a ProgressBar:
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
for (int i = 1; i <= 100; i++)
{
backgroundWorker1.ReportProgress(i);
Thread.Sleep(100);
}
}
private void backgroundWorker1_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
this.progressBar1.Value = e.ProgressPercentage;
}
Hope this helps!
The progress bar control is a UI object, and is created on the UI thread. When you call Invoke or BeginInvoke to update it, you are asking the UI thread to do the updating.
However, the UI thread is busy - in your button CLick event handler, you have a loop which Sleep()s the thread and calls prog.IncA in a loop. So it never exits back to the main UI loop (which is what dispatches windows messages and updates the UI). Your progress bar is being updated internally, but it never gets a chance to redraw because the UI thread is "busy".
The "processing" code (that is looping and calling prog.IncA()) should not be running on the UI thread at all - you need to start it off in a separate thread and then exit your Click handler so that the UI can continue to update.
Note that this has a side effect - if your UI thread is running, then the user will be able to continue interacting with your program, and so they can click again on the button and kick off another background thread - so you have to be very careful to make sure that the user can't do anything "dangerous" in the UI while you are busy processing.
I suggest you look at some introduction-to-threading tutorials to get an idea of how to use BackgroundWorker or another mechanism for running code in a separate thread. Once you understand that, you can add a progress bar. (And note that although a progress bar sounds like the simplest thing to do, it is actually rather a difficult thing to do due to the need for the UI thread to continue running but not let the user do anything dangerous during your processing)