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

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.

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.

Why BackgroundWorker is not working [duplicate]

I've been trying to figure out how to get my textbox's text or other property from within a background worker. Does anybody know how to do this? I cannot pass it as a param because it needs to be real-time. Thanks for the help!
I think you need to just invoke the property (pseudo-code):
private void bgw1_DoWork(object sender, DoWorkEventArgs e)
{
// looping through stuff
{
this.Invoke(new MethodInvoker(delegate { Text = textBox1.Text; }));
}
}
Use the ReportProgress method and event of the Background worker. That will switch to the correct thread for you.
Or if needed in WPF:
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
string text = null;
myTextBox.Dispatcher.Invoke(new Action(delegate()
{
text = myTextBox.Text;
}));
}
i think you should use invoke method.
here's my example.
delegate void myDelegate(string name);
//...
private void writeToTextbox(string fCounter)
{
if (this.InvokeRequired)
{
myDelegate textWriter = new myDelegate(displayFNums);
this.Invoke(textWriter, new object[] { fCounter });
}
else
{
textbox1.Text = "Processing file: " + fileCounter + "of" + 100;
}
}
//...
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//...
writeToTextbox(fileCounter.ToString());
}
in dowork i manipulate some textfile and i inform the user about how many files i have processed so far.

Using Backgroundworker with a Timer

I've looked in many places for this but still haven't found a solution. What I'm trying to achieve is being able to use BackgroundWorker on a timed basis. Here's an example:
public Main()
{
isDbAvail = new BackgroundWorker();
isDbAvail.DoWork += isOnline;
isDbAvail.RunWorkerCompleted += rewriteOnlineStatus;
}
private void rewriteOnlineStatus(object sender, RunWorkerCompletedEventArgs e)
{
Subs.Connection connection = new Subs.Connection();
changeStatus(connection.isDbAvail());
}
private void isOnline(object sender, DoWorkEventArgs e)
{
while (true)
{
Console.WriteLine("Checking database connection");
System.Threading.Thread.Sleep(8000);
}
}
public void changeStatus(bool status)
{
if (status)
{
serverStatusVal.Text = "Connected";
serverStatusVal.ForeColor = System.Drawing.Color.DarkGreen;
}
else
{
serverStatusVal.Text = "Not connected";
serverStatusVal.ForeColor = System.Drawing.Color.Red;
}
}
What's happening here is that the isOnline method checks if there is a connection to the database (just an example) every 8 seconds and changes the text accordingly. What I've noticed though, is that the while loop inside the isOnline method causes the rewriteOnlineStatus method never to fire because it runs indefinitely. Is there another workaround to this?
I suggest you use BackgroundWorker.ReportProgress, and check connectivity in the background thread.
Something like this:
public Main()
{
isDbAvail = new BackgroundWorker();
isDbAvail.WorkerReportsProgress = true;
isDbAvail.DoWork += isOnline;
isDbAvail.ProgressChanged += rewriteOnlineStatus;
isDbAvail.RunWorkerAsync();
}
private void rewriteOnlineStatus(object sender, ProgressChangedEventArgs e)
{
changeStatus((bool)e.UserState);
}
private void isOnline(object sender, DoWorkEventArgs e)
{
while (true)
{
Console.WriteLine("Checking database connection");
Subs.Connection connection = new Subs.Connection();
isDbAvail.ReportProgress(0, connection.isDbAvail);
System.Threading.Thread.Sleep(8000);
}
}
Now the BackgroundWorker is doing the work, and reporting back to the UI thread via ProgressChanged.

Run multiple winform instance sequentially

I have a C# winform application which needs to run multiple instance in synchronous way. The goal is to:
If the exe runs 3 times, it runs the first instance of the exe and the rest will wait until the first instance finishes the processing. Then, a next waiting exe intance will run and process and so on.
The exe will run one by one until it finish processing then the exe will terminates automatically af.
Any idea how to do this?
I already tried below:
private void CheckInstance()
{
bool _returnValue = true;
string _lockFile = string.Empty;
Random _rnd = new Random();
int _randomValue = _rnd.Next(100, 200);
int _rndmiliSec = 0;
_rndmiliSec = DateTime.Now.Millisecond * _rnd.Next(2, 6);
_lockFile = string.Concat(AppDomain.CurrentDomain.BaseDirectory, string.Format("/{0}", instanceFileName));
while (_returnValue)
{
_returnValue = File.Exists(_lockFile);
if (_returnValue)
{
Thread.Sleep(1000);
this.Hide();
}
else
{
try
{
Thread.Sleep((_rnd.Next(1000) + _rndmiliSec) + _rnd.Next(1000, 1500));
Functions.WriteLog(_lockFile, "Starting the process...");
Functions.WriteLog(_lockFile, string.Format("Start Time : {0}", paramPrintTime));
File.SetAttributes(_lockFile, FileAttributes.ReadOnly);
this.Show();
break;
}
catch (Exception)
{
_returnValue = false;
}
}
}
}
private void DeleteInstance()
{
try
{
File.SetAttributes(string.Concat(AppDomain.CurrentDomain.BaseDirectory, string.Format("/{0}", instanceFileName)), FileAttributes.Normal);
File.Delete(string.Concat(AppDomain.CurrentDomain.BaseDirectory, string.Format("/{0}", instanceFileName)));
}
catch (Exception)
{
}
}
private void Form_Shown(Object sender, EventArgs e)
{
_backWorker.RunWorkerAsync();
}
private void FormClosed(object sender, FormClosedEventArgs e)
{
DeleteInstance();
}
private void Form_Load(object sender, System.EventArgs e)
{
CheckInstance();
}
BackgroundWorker _backWorker = new BackgroundWorker();
public Form()
{
InitializeComponent();
_backWorker.WorkerReportsProgress = true;
_backWorker.ProgressChanged += _backWorker_ProgressChanged;
_backWorker.RunWorkerCompleted += _backWorker_RunWorkerCompleted;
_backWorker.DoWork += _backWorker_DoWork;
}
private void _backWorker_DoWork(object sender, DoWorkEventArgs e)
{
Do some work processing...
}
private void _backWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.Close();
}
private void _backWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
pg.Value = e.ProgressPercentage;
lblIndicator.Text = e.UserState.ToString();
}
When the exe run 3 instance, the first instance will run while 2nd and third hides for a while awaiting the 1st instance to be finisih. However, after the 1st instance finish the process, The 2nd and 3rd instance are running simultaneously.
Any Ideas? Thanks.
Maybe this can work:
public static bool IsProgramRunning(string TitleOfYourForm)
{
bool result = false;
Process[] processes = Process.GetProcesses();
foreach (Process p in processes)
{
if (p.MainWindowTitle.Contains(TitleOfYourForm))
{
result = true;
break;
}
}
return result;
}
Call this function in the Main function(before opening the mainForm), if it is false Application.Exit() else show your form..
If this answer helped you, vote me.

cross-thread calls?

This is mt first time trying to write a not web based program, and my first time writing anything in C#.
I need a program that monitors folders, but I can't get it to work.
I have used the example from this post Using FileSystemWatcher with multiple files but is trying to make it a form.
My current problem comes in the ProcessQueue function where fileList apparently is defined in another thread.
Whenever a file is actually submitted to the watched folder I get an error that using fileList is a cross thread call
Can anyone explain this error to me, and how to fix it?
namespace matasWatch
{
public partial class Form1 : Form
{
private int n = 1;
private bool isWatching = false;
private List<string> filePaths;
private System.Timers.Timer processTimer;
private string watchedPath;
private FileSystemWatcher watcher;
public Form1()
{
filePaths = new List<string>();
watchedPath = "C:\\Users\\username\\Desktop\\test";
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if (!isWatching)
{
button1.Text = "Stop";
isWatching = true;
watcher = new FileSystemWatcher();
watcher.Filter = "*.*";
watcher.Created += Watcher_FileCreated;
watcher.Error += Watcher_Error;
watcher.Path = watchedPath;
watcher.IncludeSubdirectories = true;
watcher.EnableRaisingEvents = true;
}
else {
button1.Text = "Watch";
isWatching = false;
watcher.EnableRaisingEvents = false;
watcher.Dispose();
watcher = null;
}
}
private void Watcher_Error(object sender, ErrorEventArgs e)
{
// Watcher crashed. Re-init.
isWatching = false;
button1_Click(sender, e);
}
private void Watcher_FileCreated(object sender, FileSystemEventArgs e)
{
filePaths.Add(e.FullPath);
if (processTimer == null)
{
// First file, start timer.
processTimer = new System.Timers.Timer(2000);
processTimer.Elapsed += ProcessQueue;
processTimer.Start();
}
else{
// Subsequent file, reset timer.
processTimer.Stop();
processTimer.Start();
}
}
private void ProcessQueue(object sender, ElapsedEventArgs args)
{
try
{
fileList.BeginUpdate();
foreach (string filePath in filePaths)
{
fileList.Items.Add("Blaa");
}
fileList.EndUpdate();
filePaths.Clear();
}
finally
{
if (processTimer != null)
{
processTimer.Stop();
processTimer.Dispose();
processTimer = null;
}
}
}
}
}
I assume that fileList is a windows forms control. The ProcessQueue method is called from a timer thread which is by default a background thread. The fileList control resides in the UI thread. You need to use the Invoke() method of the form passing it in a delegate the updates the fileList control.
Invoke(new Action(() =>
{
fileList.BeginUpdate();
foreach (string filePath in filePaths)
{
fileList.Items.Add("Blaa");
}
fileList.EndUpdate();
filePaths.Clear();
}));
Try using System.Windows.Forms.Timer instead of System.Timers.Timer so the timer tick event is executed on the UI thread.
See here for more details.

Categories