Background Worker to populate combobox in windows form application - c#

I want to implement background worker in my windows application.
Currently I am using button event handler to load the combo box with data. As the query hangs the user interface, i would like to implement background worker as that the query runs in different thread. I have never used this background worker in any of my application. I did some research on this and still unable to implement this. Any help or advice will be greatly appreciated.
This is how my button event handler looks like
private void button6_Click(object sender, EventArgs e)
{
if (comboBox1.SelectedItem.ToString() == "All")
{
findAllUser();
}
else
{
//Do Something!!!
}
}
findAllUser() will fetch all the user from active directory which normally takes time and makes the UI unresponsive. Code for findAllUser() looks like this.
public void findAllUser()
{
System.DirectoryServices.DirectoryEntry entry = new System.DirectoryServices.DirectoryEntry("LDAP://DC=xyz, DC=com");
System.DirectoryServices.DirectorySearcher mySearcher = new System.DirectoryServices.DirectorySearcher(entry);
mySearcher.Filter = "(&(objectClass=user))";
foreach (System.DirectoryServices.SearchResult resEnt in mySearcher.FindAll())
{
try
{
System.DirectoryServices.DirectoryEntry de = resEnt.GetDirectoryEntry();
comboBox2.Items.Add(de.Properties["GivenName"].Value.ToString() + " " + de.Properties["sn"].Value.ToString() + " " + "[" + de.Properties["sAMAccountName"].Value.ToString() + "]");
}
catch (Exception)
{
// MessageBox.Show(e.ToString());
}
}
}
Below is how the background worker looks now..all empty
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
}
Any advice how can i implement the code above so that the background worker will populate the combobox2 with the active directory user list.

The easiest way would be to have your findAllUser method build a list of items that need to be added to the combo box, and then have the RunWorkerCompleted method populate the combo box. For example, modify your findAllUser like this:
private List<string> items;
public void findAllUser()
{
items = new List<string>();
System.DirectoryServices.DirectoryEntry entry =
new System.DirectoryServices.DirectoryEntry("LDAP://DC=xyz, DC=com");
System.DirectoryServices.DirectorySearcher mySearcher =
new System.DirectoryServices.DirectorySearcher(entry);
mySearcher.Filter = "(&(objectClass=user))";
foreach (System.DirectoryServices.SearchResult resEnt in mySearcher.FindAll())
{
try
{
System.DirectoryServices.DirectoryEntry de = resEnt.GetDirectoryEntry();
items.Add(de.Properties["GivenName"].Value.ToString() + " " +
de.Properties["sn"].Value.ToString() + " " + "[" +
de.Properties["sAMAccountName"].Value.ToString() + "]");
}
catch (Exception)
{
// MessageBox.Show(e.ToString());
}
}
}
Then, have your DoWork call findAllUser to do the actual work.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
findAllUser();
}
And finally, have your RunWorkerCompleted populate the combo box:
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
foreach (var item in items)
{
comboBox2.Items.Add(item);
}
}
If you want to show progress, then you need to call ReportProgress from time to time while the worker is doing its business. Since you don't know exactly how long the process will take or exactly how many users it will find, you can't really report accurate progress. In that case, you have to guess. Since you say it takes "like 30 seconds," then you can use that as the 100% mark. So you start a StopWatch when the worker starts its processing, and update every half second or so. Modify your findAllUser function like this:
public void findAllUser()
{
const int ExpectedTime = 30000; // 30,000 milliseconds
// stopwatch keeps track of elapsed time
Stopwatch sw = Stopwatch.StartNew();
// Create a timer that reports progress at 500 ms intervals
System.Timers.Timer UpdateTimer;
UpdateTimer = new System.Threading.Timer(
null,
{
var percentComplete = (100 * sw.ElapsedMilliseconds) / ExpectedTime;
if (percentComplete > 100) percentComplete = 100;
ReportProgress(percentComplete);
// Update again in 500 ms if not already at max
if (percentComplete < 100)
UpdateTimer.Change(500, Timeout.Infinite);
}, 500, Timeout.Infinite);
items = new List<string>();
// rest of findAllUser here
// dispose of the timer.
UpdateTimer.Dispose();
}
And then, in your ProgressChanged event handler, update your progress bar.
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Update the progress bar with the value from e.ProgressPercentage
}
Again, because you don't know exactly how long things are going to take, you make an estimate. Above, I estimated 30 seconds, and the code blindly assumes that if 15 seconds have gone by, then it's half done.
Note that I create the timer as a one-shot and re-initialize it after every tick. I do this because I want to prevent concurrent updates. The timer fires on a separate thread, and the ReportProgress method marshals the call to the ProgressChanged event to the UI thread. If the UI thread is busy with other things, then another timer event could come in and you could end up with a bunch of threads all trying to marshal calls to the UI. Probably not a problem in this case because we're only talking a maximum of 60 calls (two calls per second for 30 seconds), but in general it's a good idea to prevent that kind of thing from happening.

Place your code on the backgroundWorker1_DoWork. But I suggest you either use Thread or Task Parallel Library
If you're on .NET 4.0, use the TPL.
You can do it like this:
Task runner = new Task(() =>
{
// do process here
});
runner.Start();
of if you're on older frameworks, use the Thread like this.
Thread thread = new Thread(() =>
{
// do process here
});
thread.IsBackground = true;
thread.Start();
read more about the TPL and Thread.

Using BackgroundWorker is convenient because it automagically invokes the ProgressChanged and RunworkerCompleted event handlers in the UI thread. You can use it like below.
private void AddItem(DirectoryEntry de)
{
comboBox2.Items.Add(de.Properties["GivenName"].Value.ToString() + " " + de.Properties["sn"].Value.ToString() + " " + "[" + de.Properties["sAMAccountName"].Value.ToString() + "]");
}
private void button6_Click(object sender, EventArgs e)
{
if (comboBox1.SelectedItem.ToString() == "All")
{
this.backgroundWorker1.RunWorkerAsync();
}
else
{
//Do Something!!!
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Bind to the users container.
DirectoryEntry entry = new DirectoryEntry("LDAP://CN=xyz,DC=com");
// Create a DirectorySearcher object.
DirectorySearcher mySearcher = new DirectorySearcher(entry);
try
{
// Create a SearchResultCollection object to hold a collection of SearchResults
// returned by the FindAll method.
SearchResultCollection result = mySearcher.FindAll();
int count = result.Count;
for(int i = 0; i < count; i++)
{
SearchResult resEnt = result[i];
try
{
DirectoryEntry de = resEnt.GetDirectoryEntry();
BeginInvoke(new Action<DirectoryEntry>(AddItem), de);
}
catch (Exception)
{
// MessageBox.Show(e.ToString());
}
this.backgroundWorker1.ReportProgress(i / count);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.progressBar1.Value = e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.progressBar1.Value = 100;
}

You can use below logic to implement background worker for your code.
var startListenerWorker = new BackgroundWorker();
startListenerWorker.DoWork += new DoWorkEventHandler(this.StartListenerDoWork);
startListenerWorker.RunWorkerAsync();
private void StartListenerDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
{
// Your logic to load comboBox will go here for running your query
}
you can also implement thread to have your logic run on a separate thread.

Related

UI unresponsive + progress bar not working while using backgroundworker c#

I'm working on a WPF application in Visual Studio, I need to download a large file and extract it in my code. Someone recommended that I use background workers, but now when I try to increase the value on my progress bar it doesn't work... Can anyone help?
public void InstallVersion(string version)
{
string location = File.ReadAllText(#"C:\Users\" + Environment.UserName + #"\AppData\Roaming\MidnightFallsLauncher\data\locator.txt");
location = location + #"\Versions\" + version;
if (File.Exists(location + ".zip"))
File.Delete(location + ".zip");
if (Directory.Exists(location))
{
DirectoryInfo di = new DirectoryInfo(location);
foreach (FileInfo file in di.GetFiles())
{
file.Delete();
}
foreach (DirectoryInfo dir in di.GetDirectories())
{
dir.Delete(true);
}
}
if (!myWorker.IsBusy)
{
myWorker.RunWorkerAsync();
}
}
And here is my worker code
public MainWindow()
{
InitializeComponent();
myWorker.DoWork += new DoWorkEventHandler(myWorker_DoWork);
myWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(myWorker_RunWorkerCompleted);
myWorker.ProgressChanged += new ProgressChangedEventHandler(myWorker_ProgressChanged);
myWorker.WorkerReportsProgress = true;
myWorker.WorkerSupportsCancellation = true;
}
protected void myWorker_DoWork(object sender, DoWorkEventArgs e)
{
string location = File.ReadAllText(#"C:\Users\" + Environment.UserName + #"\AppData\Roaming\MidnightFallsLauncher\data\locator.txt");
location = location + #"\Versions\" + Version;
WebClient Client = new WebClient();
string url = "";
string content = "";
string downloadlink = "";
List<string> availibleVersions = new List<string>();
List<string> versionDownload = new List<string>();
url = "https://midnightfalls.glitch.me/versions.html";
content = Client.DownloadString(url);
foreach (string line in content.Split(new string[] { "<br>", "<br />" }, StringSplitOptions.None))
{
if (line.Contains("0"))
{
availibleVersions.Add(line);
}
}
url = "https://midnightfalls.glitch.me/versionslink.html";
content = Client.DownloadString(url);
foreach (string line in content.Split(new string[] { "<br>", "<br />" }, StringSplitOptions.None))
{
if (line.Contains("https"))
{
versionDownload.Add(line);
}
}
for (var i = 0; i < availibleVersions.Count; i++)
{
if (availibleVersions[i] == Version)
{
downloadlink = versionDownload[i];
}
}
Client.DownloadFile(downloadlink, location + ".zip");
ZipFile.ExtractToDirectory(location + ".zip", location);
File.Delete(location + ".zip");
RunGame(Version);
}
protected void myWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
}
protected void myWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.Dispatcher.Invoke(() =>
{
progress.Value += 10;
});
}
Also, while my worker is running, the UI freezes... I'm pretty sure that's not meant to happen.
EDIT:
The UI is now updating but the progress bar still doesnt work...
It looks like the problem is that the main thread is constantly working while the background worker is doing its work:
while (this.myWorker.IsBusy)
{
this.Dispatcher.Invoke(() =>
{
progress.Value += 10;
});
}
means that your main thread is constantly doing stuff while the background job is working, which is why the UI doesn't update.
You need to move the progress update to the background worker (where you could also set a value that actually makes some sense, e.g. to indicate how many of the 'availableVersion's you have downloaded).
Hope that makes sense.
EDIT:
Suppose we'll put all the code directly in the view, so assume we have a progress bar named 'progressBar' and a button named 'btnStart' (which kicks of the backgroundworker).
Here's the codebehind:
private BackgroundWorker worker;
public MainWindow()
{
InitializeComponent();
this.worker = new BackgroundWorker();
this.worker.DoWork += new DoWorkEventHandler(myWorker_DoWork);
this.worker.RunWorkerCompleted += new
RunWorkerCompletedEventHandler(myWorker_RunWorkerCompleted);
this.worker.ProgressChanged += new
ProgressChangedEventHandler(myWorker_ProgressChanged);
this.worker.WorkerReportsProgress = true;
this.worker.WorkerSupportsCancellation = true;
}
private void myWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.progressBar.Value = e.ProgressPercentage;
}
private void myWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Whatever you need to do when finished here (alert, update a label, etc.)
}
private void myWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Just loop and report new progress. Sleep a little in between each
// progress update so that it isn't over before we have a chance to see it.
for(int i=0;i<100;i++)
{
Thread.Sleep(200);
this.worker.ReportProgress(i);
}
}
private void btnStart_Click(object sender, RoutedEventArgs e)
{
this.worker.RunWorkerAsync();
}
What happens is that the background worker fires an event notifying that progress has changed.
The main thread has a handle on that event, and updates the progress bar.
Since it's a background worker you don't need to use Dispatcher.Invoke - this is already taken care of.
Hope the example clarifies things for you.
I'm pretty sure you're blocking right here:
while (this.myWorker.IsBusy)
{
this.Dispatcher.Invoke(() =>
{
progress.Value += 10;
});
}
You should be calling ReportProgress on your BackgroundWorker instance within your myWorker_DoWork method.
Also, if you're using .NET 4.5 or later, you can dump the BackgroundWorker entirely and rewrite this code using the async/await pattern.

Passing argument to Backgroundworker error handler

My program has to test several products in different slots simultaneously. When there's an error in a slot like accidental detachment from the computer, the program is suppose to log the error type and the serial number of the product that has been provided by the user when starting up the UI into a textfile.
I'm using Background Worker to handle the multi-threading. While I have managed to log the error type using e.Error, I can't seem to figure out how to pass the serial number from the DoWork function to the Background Worker error handler.
I tried googling for a solution but it seems like nobody has asked this before. I will really appreciate any help given.
PS: I'm quite new to C# so be gentle haha :)
Below is an example code:
private void startAsync_Click(object sender, EventArgs e)
{
if (backgroundWorker1.IsBusy != true)
{
// Start the asynchronous operation.
backgroundWorker1.RunWorkerAsync();
}
}
private void cancelAsync_Click(object sender, EventArgs e)
{
if (backgroundWorker1.WorkerSupportsCancellation == true)
{
// Cancel the asynchronous operation.
backgroundWorker1.CancelAsync();
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
int b = 0; //simulate error
for (int i = 1; i <= 10; i++)
{
if (worker.CancellationPending == true)
{
string[] array2 = { "1", "cancelled" };
e.Result = array2; //passing values when user cancel through e.Result object
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
worker.ReportProgress(i * 10, "Test a");
int a = 1 / b; //simulate error
System.Threading.Thread.Sleep(1000);
}
string[] array1 = {"1","done"};
e.Result = array1; //passing values when complete through e.Result object
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
resultLabel.Text = e.ProgressPercentage.ToString() + "%" + e.UserState.ToString();
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled == true)
{
string[] someArray2 = e.Result as string[];
string sernum = someArray2[0];
string status = someArray2[1];
resultLabel.Text = sernum + " " + status;
}
else if (e.Error != null)
{
resultLabel.Text = "Error: " + e.Error.Message; //how to pass sernum here?
}
else
{
string[] someArray = e.Result as string[];
string sernum = someArray[0];
string status = someArray[1];
resultLabel.Text = sernum + " " + status;
}
}
There are lots of different ways to get data back to the RunWorkerCompleted event handler in the case of an exception.
IMHO, the most natural from a semantic point of view is to put the data in the exception itself. For example:
class BackgroundWorkerException : Exception
{
public string Sernum { get; }
public BackgroundWorkerException(string sernum, Exception inner)
: base("DoWork event handler threw an exception", inner)
{
Sernum = sernum;
}
}
Then in your DoWork handler:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
try
{
int b = 0; //simulate error
for (int i = 1; i <= 10; i++)
{
if (worker.CancellationPending == true)
{
string[] array2 = { "1", "cancelled" };
e.Result = array2; //passing values when user cancel through e.Result object
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
worker.ReportProgress(i * 10, "Test a");
int a = 1 / b; //simulate error
System.Threading.Thread.Sleep(1000);
}
string[] array1 = {"1","done"};
e.Result = array1; //passing values when complete through e.Result object
}
}
catch (Exception e)
{
throw new BackgroundWorkerException("1", e);
}
}
Finally, in the RunWorkerCompleted event handler:
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled == true)
{
string[] someArray2 = e.Result as string[];
string sernum = someArray2[0];
string status = someArray2[1];
resultLabel.Text = sernum + " " + status;
}
else if (e.Error != null)
{
string sernum = ((BackgroundWorkerException)e.Error).Sernum;
resultLabel.Text = "Error: " + e.Error.Message;
}
else
{
string[] someArray = e.Result as string[];
string sernum = someArray[0];
string status = someArray[1];
resultLabel.Text = sernum + " " + status;
}
}
Your question isn't clear about what sernum actually represents, and in particular whether it's a single value for a given background task, or a single task could have more than one value for sernum. If it's the former, i.e. you know when you start the task what the value is, then you could pass it directly to the event handlers by capturing it in an anonymous method used for each actual event handler.
That approach won't work though in your specific scenario without some changes. You appear to have added a single BackgroundWorker object to your form as a component and are reusing it. Using an anonymous method works better/more easily if you are creating a new BackgroundWorker each time, so that you can subscribe your anonymous method delegate to DoWork and RunWorkerCompleted. (You have to subscribe it just before each invocation because, presumably, the sernum value is different each time.)
You could get it to work with the single component added to the form in the Designer as you're doing here, but it's a lot more complicated because you have to dynamically add a handler to the RunWorkerCompleted event which unsubscribes both itself and the delegates you subscribed to the DoWork and RunWorkerCompleted events (you wouldn't subscribe any methods directly to the component in the Designer, in this scheme).
Another alternative is to create a custom data structure passed as the argument for RunWorkerAsync(), which can contain a property for the sernum value. You can set this value in the method that starts the worker, or in the DoWork event handler.
This approach fits only a little better with the component-in-Designer scenario you have, because you still need a way to get the reference to that custom data structure back to the RunWorkerCompleted event handler, which you can do only by storing it in e.g. an instance field that can be shared between the Click event handler that starts the worker and the RunWorkerCompleted event (and frankly, if you do that, at that point it's debatable whether it's even worth it to pass that reference to the RunWorkerAsync() method, since the DoWork event handler could get at the same instance field just as well.)
Another alternative is to catch the exception as I've done in my code example above, but then instead of rethrowing the exception, treat it as if the work was cancelled (i.e. set the Result and Cancel properties).
Yet another approach is to abandon BackgroundWorker altogether and switch to the TPL Task-based idiom. That doesn't solve the problem implicitly, but it allows any of the above options, as well as the option of just defining your own mode for passing errors back.
If you need more specific help than that, you'll need to post a new question, with a good Minimal, Complete, and Verifiable code example that shows which of the above approaches you've attempted to try, or some other alternative not listed here, and what specifically you're unable to figure out.
See code below. You don't need a class. Can simple send a string or int using similar code.
public class Parameters
{
public string message = "";
}
private void startAsync_Click(object sender, EventArgs e)
{
if (backgroundWorker1.IsBusy != true)
{
// Start the asynchronous operation.
Parameters parameters = new Parameters() { message = "The quick brown fox jumped over the lazy dog" };
backgroundWorker1.RunWorkerAsync(parameters);
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Parameters parameters = e.Argument as Parameters;
}

How to trigger an event upon progress bar completion

I have created a Windows Forms program that breaks down a file and sends its components to a server. These files are large, so I created a progressBar so users don't think it froze while the transactions are happening. What I would like to do is have some mechanism that will actively trigger only when all threads are complete without blocking the UI thread (again, so the plebs wont think its frozen). The best I could come up with is a kind of passive "wait until true" but I feel like there has to be a better way to do this. I have experimented with trying to create an event or a callback but honestly I've just ended up more confused than when I started. Here is an example of how I am doing this now:
public partial class Program : Form
{
private readonly OpenFileDialog _ofd = new OpenFileDialog();
public delegate void BarDelegate();
private string _path;
private void button1_Click(object sender, EventArgs e)
{
if (_ofd.ShowDialog() != DialogResult.OK) return;
textBox1.Text = _ofd.SafeFileName;
_path = _ofd.FileName;
}
private void button2_Click(object sender, EventArgs e)
{
var allLinesFromFile = File.ReadAllLines(_path);
progressBar1.Minimum = 0;
progressBar1.Maximum = allLinesFromFile.Length;
Task.Factory.StartNew(() => Parallel.ForEach(allLinesFromFile, DoSomething));
while (progressBar1.Value < progressBar1.Maximum) //there has to be a better way to do this...
{
MessageBox.Show("Please wait.", "Record Poster", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
}
//some processes here which should only take place after all threads are complete.
var postingComplete = MessageBox.Show("The posting is complete!", "Record Poster", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
if (postingComplete == DialogResult.OK) Environment.Exit(0);
}
private void DoSomething(string record)
{
//some string manipulation and server transactions here
BeginInvoke(new BarDelegate(() => progressBar1.Increment(1)));
}
}
Try using Microsoft's Reactive Framework (NuGet "System.Reactive.Windows.Forms") for this. Then your code becomes:
private void button2_Click(object sender, EventArgs e)
{
var allLinesFromFile = File.ReadAllLines(_path);
progressBar1.Minimum = 0;
progressBar1.Maximum = allLinesFromFile.Length;
IDisposable subscription =
allLinesFromFile
.ToObservable()
.SelectMany(f => Observable.Start(() => DoSomething(f)))
.ObserveOn(this)
.Do(x => progressBar1.Value += 1)
.Subscribe(x => { }, () =>
{
var postingComplete = MessageBox.Show("The posting is complete!", "Record Poster", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
if (postingComplete == DialogResult.OK)
{
Application.Exit();
}
});
}
private void DoSomething(string record)
{
System.Threading.Thread.Sleep(5);
}
If you need to stop this early then just call subscription.Dispose(). I've tested this and it works fine for me.
You should be using the BackGroundWorker class, see: How to use a BackgroundWorker?
And use BackGroundWorker.RunWorkerComplete for when the thread has finished
Background worker:
**Backgroundworker (System.ComponentModel)**
BackgroundWorker loader = new BackgroundWorker();
loader.DoWork += load_Specials_BW_Thread;
loader.WorkerReportsProgress = true;
loader.ProgressChanged += load_Special_Feeds_Progress_Changed;
private void load_Specials_BW_Thread(object sender, DoWorkEventArgs e)
{
int pctComplete = (int)Math.Floor(ptComplete * 100);//recs done / total recs
(sender as BackgroundWorker).ReportProgress(pctComplete);
}
Good luck!

C# BackgroundWorker ProgressChanged doesn't get fired until end of function

I have a method in my class that has some loops inside.
Main purpose of this method is converting some files so I put a progressbar in my form that should get updated after each file has been converted.
I tried every possible combination and I read everything I could but I couldn't solve this issue.
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
converterProgressBar.Value = e.ProgressPercentage;
}
is called only after the main loop of my method has been executed.
This is my method:
public string Convert()
{
convertBtn.Enabled = false;
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
totalCount = files.length;
bw.RunWorkerAsync();
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}
foreach (string file in files)
{
countFile++;
if (chk.Checked)
{
class1.DoJob();
}
using (// some code))
{
using (//some other code))
{
try
{
using (// again some code)
{
// job executing
}
}
catch (exception
{
}
}
}
convertedVideosL.Text = txtToUpdate;
convertedVideosL.Refresh();
}
countFile = countFile + 1;
MessageBox.Show("Done");
countFile = -1;
return outputFile;
}
And here are the BackgroundWorker Event Handlers:
void bw_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i <= totalCount; i++)
{
if (bw.CancellationPending)
{
e.Cancel = true;
}
else
{
int progress = Convert.ToInt32(i * 100 / totalCount);
(sender as BackgroundWorker).ReportProgress(progress, i);
}
}
}
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
converterProgressBar.Value = e.ProgressPercentage;
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled == false)
{
convertedVideosL.Text = "Finished!";
}
else
{
convertedVideosL.Text = "Operation has been cancelled!";
}
}
But I cannot get to update the progress bar for every file that is converted.
It waits for the foreach loop to end and then calls bw_ProgressChanged.
If I put RunWorkerAsync() inside the foreach loop an exception is thrown that says the BackgroundWorker is busy and cannot execute other tasks.
It seems to me obvious that DoWork() only executes a for loop then it shouldn't be aware of the conversion going on but ProgressChanged should be fired by ReportProgress(progress,i).
Could please someone explain me why and help me with a solution?
Thanks!
Currently the conversion is not executed by the instance of the BackgroundWorker type. The conversion should be called from the DoWork event handler.
Please consider extracting the conversion-related functionality:
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}
foreach (string file in files)
{
// Details...
}
into the separate method. After that just call the method from the DoWork event handler.
Pseudo-code to demonstrate the idea:
public void StartConversion()
{
...
TWorkerArgument workerArgument = ...;
worker.RunWorkerAsync(workerArgument);
// No message box here because of asynchronous execution (please see below).
}
private void BackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
{
// Get the BackgroundWorker that raised this event.
BackgroundWorker worker = sender as BackgroundWorker;
e.Result = Convert(worker, (TWorkerArgument)e.Argument);
}
private static TWorkerResult Convert(BackgroundWorker worker, TWorkerArgument workerArgument)
{
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}
foreach (string file in files)
{
// Details...
worker.ReportProgress(percentComplete);
}
return ...;
}
private void BackgroundWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Show the message box here if required.
}
Please replace the TWorkerArgument and TWorkerResult types appropriately.
Also, please refer to the example which uses the BackgroundWorker class for the additional details: How to: Implement a Form That Uses a Background Operation, MSDN.

C# InvalidOperationException AND Cross-thread operation [duplicate]

This question already has answers here:
Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on
(22 answers)
Closed 4 years ago.
In my windows form I have a text box and a button, the text box “tb_LogBox” is multiline text box I am trying to create a background worker that is supposed to call a function i.e. “LogTimer.DnT()” when I compile is and run it Visual studio throws InvalidOperationException.
The actual Error I am getting
Cross-thread operation not valid: Control 'tb_LogBox' accessed from a thread other than the thread it was created on. The following sample code illustrate what I am trying to do
private void button1_Click(object sender, EventArgs e)
{
try
{
var bw = new BackgroundWorker();
bw.DoWork += ExecuteOperations ;
bw.RunWorkerAsync();
}
catch (Exception ex)
{
tb_LogBox.AppendText(Environment.NewLine + " =#= " + ex.Message+" "+ex.Source);
}
}
private void ExecuteOperations(object sender, DoWorkEventArgs e)
{
var FuncCall = new LogTimer();
tb_LogBox.AppendText(Environment.NewLine + FuncCall.DnT()); // the line i am getting the error. on
}
public class LogTimer
{
public string DnT()
{
const string datePat = #"d/MM/yyyy";
var dateTime = DateTime.Now();
return dateTime.ToString(datePat);
}
}
Try to use the begin invoke method :
BeginInvoke(new Action(() =>
{
tb_LogBox.AppendText(Environment.NewLine + FuncCall.DnT());
}));
This would be smoother than Invoke.
you need to marshall the Ui change onto the UI thread. This can be performed by using an invoke/begininvoke call around your tb_LogBox.AppendText
in a Winforms Application:
this.BeginInvoke((MethodInvoker)delegate
{
tb_LogBox.AppendText(Environment.NewLine + FuncCall.DatenTime());
});
in a WPF application:
this.Dispatcher.BeginInvoke(
(Action)delegate()
{
tb_LogBox.AppendText(Environment.NewLine + FuncCall.DatenTime());
});
Hope this helps!
Do this in your ExecuteOperations:
tb_LogBox.Invoke((MethodInvoker)delegate() { tb_LogBox.AppendText(...) }));
You cannot use other threads (BackgroundWorker uses a .NET threadpool thread) to make changes to UI components. This is a major hurdle you will have to get used to in WinForms programming.
The BackgroundWorker is executing on its own thread and all operations related to WinForms GUI elements must run on the thread they were created on. They way you currently use the BackgroundWorker is identical to just Queuing the operation using ThreadPool.QueueUserWorkItem(). For communication back to the GUI using a BackgroundWorker, use ReportProgess or set the DoWorkEventArgs.Result property in the worker method, and react to the corresponding events on the GUI thread. You can also use Invoke/BeginInvoke on a WinForms control to execute arbitrary code directly on the GUI thread. In your case that would mean replacing the line accessing the tb_LogBox with:
tb_LogBox.Invoke(new Action(() =>
tb_LogBox.AppendText(Environment.NewLine + FuncCall.DatenTime());
));
You need to invoke the control's method on the UI thread:
private void ExecuteOperations(object sender, DoWorkEventArgs e)
{
var FuncCall = new LogTimer();
tb_LogBox.Invoke((MethodInvoker)delegate{
tb_LogBox.AppendText(Environment.NewLine + FuncCall.DatenTime());
});
}
I don't know what LogTimer does, but it may very well be that you should create that inside the delegate as well:
private void ExecuteOperations(object sender, DoWorkEventArgs e)
{
tb_LogBox.Invoke((MethodInvoker)delegate{
var FuncCall = new LogTimer();
tb_LogBox.AppendText(Environment.NewLine + FuncCall.DatenTime());
});
}
You can't access the host thread from the background worker's execution thread. You can use the ReportProgress method of the BackgroundWorker to send information to the host thread.
private void button1_Click(object sender, EventArgs e)
{
try
{
var bw = new BackgroundWorker();
bw.DoWork += ExecuteOperations;
bw.ProgressChanged += bw_ProgressChanged;
bw.RunWorkerAsync();
}
catch (Exception ex)
{
tb_LogBox.AppendText(Environment.NewLine + " =#= " + ex.Message + " " + ex.Source);
}
}
private static void ExecuteOperations(object sender, DoWorkEventArgs e)
{
var FuncCall = new LogTimer();
string text = Environment.NewLine + FuncCall.DnT();
(sender as BackgroundWorker).ReportProgress(0, text);
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
tb_LogBox.AppendText(e.UserState as string);
}
public class LogTimer
{
public string DnT()
{
const string datePat = #"d/MM/yyyy";
var dateTime = DateTime.Now;
return dateTime.ToString(datePat);
}
}

Categories