I'm facing an issue with BackgroundWorker in C#, hope you can help.
Here are some piece of code on my program
var bwBIG = new BackgroundWorker();
bwBIG.DoWork += (_senderBIG, argsBIG) =>
{
var bw = new BackgroundWorker();
String requestResult = "";
bw.DoWork += (_sender, args) =>
{
requestResult = tempHelper.GetData();
};
bw.RunWorkerCompleted += (_sender, args) =>
{
makeData(requestResult);
};
bw.RunWorkerAsync();
}; //bwBIG.DoWork
bwBIG.RunWorkerCompleted += (_senderBIG, argsBIG) =>
{
prepareData(rtbData.Text);
MessageBox.Show("Data OK");
};
bwBIG.RunWorkerAsync();
It suppose to run the tempHelper.GetData() , and then makeData(requestResult), and then finnally prepareData(rtbData.Text) then MessageBox.Show("Data OK");
However, it seems the bw is not running at all, whenever I run the code, it just jumped strange to MessageBox.Show("Data OK");
Any help please???
Thanks for reading.
It's kinda useless to create backgroundworkerthread inside a worker thread.
I would keep it this way:
var localCopy = rtbData.Text;
var bwBIG = new BackgroundWorker();
bwBIG.DoWork += (_senderBIG, argsBIG) =>
{
var requestResult = tempHelper.GetData();
makeData(requestResult);
prepareData(localCopy);
MessageBox.Show("Data OK");
}; //bwBIG.DoWork
bwBIG.RunWorkerAsync();
It's kinda hard to see the flow of data.
Related
I used to be weary of using BackgroundWorker because it required so many functions to work correctly. However when I swapped to C# from VB.NET (about a month ago) I stumbled across a really easy way to instance them;
Example;
private void cmdMaxCompressPNG_Click(object sender, EventArgs e) {
pbStatus.Maximum = lstFiles.Items.Count;
List<string> FileList = Load_Listbox_Data();
var bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += delegate {
foreach (string FileName in FileList) {
ShellandWait("optipng.exe", String.Format("\"{0}\"", FileName));
bw.ReportProgress(1);
}
};
bw.ProgressChanged += (object s, ProgressChangedEventArgs ex) => {
pbStatus.Value += 1;
};
bw.RunWorkerCompleted += delegate {
lstFiles.Items.Clear();
pbStatus.Value = 0;
MessageBox.Show(text: "Task Complete", caption: "Status Update");
};
bw.RunWorkerAsync();
}
There it is, all in one function! Simple to write, easy to understand, and no real leg work. I even made a Snippet out of it. I've since converted all my multiple part BackgroundWorker functions, into this little piece of elegant code. I've also started using them more liberally than in the past. Yesterday I was reading an article regarding Async and Await and how that's apparently how I should be doing things. I'm having trouble wrapping my head around it.
I've tried to use local functions, but I can't get the wording correct. It keeps trying to put it as synchronous.
How would I convert the above into an equally tight implementation of Await/Async logic?
[Edit]
ShellandWait;
private void ShellandWait(string ProcessPath, string Arguments, bool boolWait = true) {
System.Diagnostics.Process ShellProcess = new System.Diagnostics.Process();
ShellProcess.StartInfo.FileName = ProcessPath;
ShellProcess.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
ShellProcess.StartInfo.Arguments = Arguments;
ShellProcess.StartInfo.CreateNoWindow = true;
ShellProcess.StartInfo.UseShellExecute = false;
ShellProcess.StartInfo.RedirectStandardOutput = true;
ShellProcess.Start();
if (boolWait) { ShellProcess.WaitForExit(); }
if (boolWait) { ShellProcess.Close(); }
}
The original code processes only one file at a time so you could use a simple loop and only execute ShellandAwait asynchronously:
private void cmdMaxCompressPNG_Click(object sender, EventArgs e)
{
pbStatus.Maximum = lstFiles.Items.Count;
var FileList = Load_Listbox_Data();
foreach (var FileName in FileList)
{
//Only thing that needs to run in the background
await Task.Run(()=>ShellandWait("optipng.exe", String.Format("\"{0}\"", FileName));
//Back in the UI
pbStatus.Value += 1;
}
};
lstFiles.Items.Clear();
pbStatus.Value = 0;
MessageBox.Show(text: "Task Complete", caption: "Status Update");
It would be even better if ShellandWait was modified so it *doesn't block. I assume it uses Process.WaitForExit() to block. The method should await asynchronously instead by listening to the Exited event. Such events can be converted to tasks as shown in Tasks and the Event-based Asynchronous Pattern.
The method would look something like this :
Task<string> ShellAsync(string commandPath,string argument)
{
var tcs = new TaskCompletionSource<string>();
var process = new Process();
//Configure the process
//...
process.EnableRaisingEvents = true;
process.Exited += (s,e) => tcs.TrySetResult(argument);
process.Start();
return tcs.Task;
}
This would allow the loop to be simplified to :
foreach (var FileName in FileList)
{
await ShellAsync("optipng.exe", String.Format("\"{0}\"", FileName));
//Back in the UI
pbStatus.Value += 1;
}
The way I went about this (after reading some more) is to use Task.Run()
private async void cmdMaxCompressPNG_Click(object sender, EventArgs e) {
pbStatus.Maximum = lstFiles.Items.Count;
List<string> FileList = Load_Listbox_Data();
await Task.Run(() => {
foreach (string FileName in FileList) {
ShellandWait("optipng.exe", String.Format("\"{0}\"", FileName));
pbStatus.GetCurrentParent().Invoke(new MethodInvoker(delegate { pbStatus.Value += 1; }));
}
});
lstFiles.Items.Clear();
pbStatus.Value = 0;
MessageBox.Show(text: "Task Complete", caption: "Status Update");
}
Note the async next to private.
I had to get fancy with the progress bar since it was a status strip progress bar. If it would have been a standard control I could have used;
pbStatus.Invoke((Action)(() => pbStatus.Value += 1))
Answer to progress bar found at -> Update progress bar from Task.Run async
And here -> How to Invoke the progress bar in Status strip?
I'd consider doing this with Microsoft's Reactive Framework. I think it's much more powerful than using Tasks.
private void cmdMaxCompressPNG_Click(object sender, EventArgs e)
{
pbStatus.Maximum = lstFiles.Items.Count;
var query =
from FileName in Load_Listbox_Data().ToObservable()
from u in Observable.Start(() =>
System.Diagnostics.Process
.Start("optipng.exe", String.Format("\"{0}\"", FileName))
.WaitForExit())
select u;
query
.ObserveOn(this) //marshall back to UI thread
.Subscribe(
x => pbStatus.Value += 1,
() =>
{
lstFiles.Items.Clear();
pbStatus.Value = 0;
MessageBox.Show(text: "Task Complete", caption: "Status Update");
});
}
Just NuGet "System.Reactive.Windows.Forms" and add using System.Reactive.Linq; to get it working.
I can't find out what is wrong in the code below. Since Parallel.For is freezing the form, I used a backgroundworker. However, it doesn't return the string
as intended. Also I'm not sure about how to use report for progressbar in backgroundworker. I don't think I need the variable int i; but without it, I can't report the progress.
private string trdoc(string str)
{
string alltrdoc;
var alldoc = str.Split('\n');
string[] alltrdoc1 = new string[alldoc.Length];
pb1.Maximum = alldoc.Length;//progressbar
pb1.Value = 0;
int i = 0;
BackgroundWorker bw = new BackgroundWorker();
// this allows our worker to report progress during work
bw.WorkerReportsProgress = true;
// what to do in the background thread
bw.DoWork += new DoWorkEventHandler(
delegate (object o, DoWorkEventArgs e)
{
Parallel.For(0, alldoc.Length, new ParallelOptions { MaxDegreeOfParallelism = 4 },
index =>
{
alltrdoc1[index] = translate(alldoc[index]);
bw.ReportProgress(i++);
});
e.Result = alltrdoc1;
});
bw.ProgressChanged += new ProgressChangedEventHandler(
delegate (object o, ProgressChangedEventArgs e)
{
if (pb1.Value <= pb1.Maximum)
{
pb1.Value++;
}
});
// what to do when worker completes its task (notify the user)
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(
delegate (object o, RunWorkerCompletedEventArgs e)
{
alltrdoc1 = e.Result as string[];
});
bw.RunWorkerAsync();
alltrdoc = string.Join(Environment.NewLine, alltrdoc1);
saveposcdic();
return alltrdoc;
}
The end of your trdoc() method is where the problem is in your current implementation:
bw.RunWorkerAsync();
alltrdoc = string.Join(Environment.NewLine, alltrdoc1);
saveposcdic();
return alltrdoc;
bw.RunWorkerAsync returns immediately, so alltrdoc will be an empty string. The alltrdoc1 array is only fully populated once RunWorkerCompleted executes. It´s there that you would create alltrdoc and continue.
I have modified code, but now I have another problem. The InvalidOperation exception occurs inside if statement on validating user info. It says that the calling thread cannot access this object because a different thread owns it.Any sugestions?
private void finishConfigButton_Click(object sender, RoutedEventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
bool validated = false;
errorLabel.Visibility = System.Windows.Visibility.Collapsed;
validationProfile.IsBusy = true;
finishConfigButton.IsEnabled = false;
backToLoginHyperlink.IsEnabled = false;
worker.DoWork += (o, ea) =>
{
if (newUser.ValidateNewUserInformation(newNameTextBox.Text, newEmailTextBox.Text, newUsernameTextBox.Text, newPasswordPasswordBox.Password, ref errorLabel))
{
validated = true;
string activeDir = Environment.SystemDirectory.Substring(0, 1) + #":\Users\" + Environment.UserName + #"\My Documents\SSK\Users";
string newPath = System.IO.Path.Combine(activeDir, newUser.Username);
Directory.CreateDirectory(newPath);
newUser.SaveUserData(newUser);
newPath = System.IO.Path.Combine(activeDir, newUser.Username + #"\Settings");
Directory.CreateDirectory(newPath);
newUserSettings.SetDefaultValues();
newUserSettings.SaveSettings(newUser, newUserSettings);
}
else
validated = false;
if (worker.CancellationPending)
{
ea.Cancel = true;
return;
}
};
worker.RunWorkerCompleted += (o, ea) =>
{
validationProfile.IsBusy = false;
finishConfigButton.IsEnabled = true;
backToLoginHyperlink.IsEnabled = true;
};
worker.RunWorkerAsync(this);
if (validated)
{
IntelliMonitorWindow intelliMonitor = new IntelliMonitorWindow(newUser, newUserSettings);
intelliMonitor.Show();
this.Close();
}
else
errorLabel.Visibility = System.Windows.Visibility.Visible;
}
What you are doing here is running everything on the UI thread. This means that while the heavy code is running, you are blocking the UI from repainting, and hence the validationProfile is not updated untill the end of the method, where IsBusy is set to false.
What you need to do is to process the heavy code into a new thread, which can update the UI at the same time.
Take a look at this blog post written by Brian Lagunas, the creator of Extended Toolkit:
http://elegantcode.com/2011/10/07/extended-wpf-toolkitusing-the-busyindicator/
He explains how to use the BusyIndicator with a BackgroundWorker.
The busy indicator in your XAML code does not have any content. Put some control(s) into it:
<wpfet:BusyIndicator Name="validationProfile" IsBusy="False" BusyContent="Working...Please wait" DisplayAfter="0" Background="DimGray">
<Grid>
...
</Grid>
</wpfet:BusyIndicator>
If you change to busy, those controls will get disabled and the BusyIndicator will appear above them.
I suppose you want to wrap the whole <Grid Background="LightGray"> with the BusyIndicator.
Use a background worker or a new thread to run the heavy process and set the UI thread free. This helps you to update the UI even when the background process is running
Eg:
public void finishConfigButton_Click()
{
worker = new BackgroundWorker();
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
//Do the heavy work here
};
worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
{
//Things to do after the execution of heavy work
validationProfile.IsBusy = false;
};
validationProfile.IsBusy= true;
worker.RunWorkerAsync();
}
}
I finally figure it out. You can't use UI objects inside worker.DoWork block.I slightly modified the code and it now works.
private void finishConfigButton_Click(object sender, RoutedEventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
errorLabel.Visibility = System.Windows.Visibility.Collapsed;
validationProfile.IsBusy = true;
finishConfigButton.IsEnabled = false;
backToLoginHyperlink.IsEnabled = false;
bool validated = false;
string newName = newNameTextBox.Text;
string newEmail = newEmailTextBox.Text;
string newUsername = newUsernameTextBox.Text;
string newPassword = newPasswordPasswordBox.Password;
string errorMessage = "Unknown error.";
worker.DoWork += (o, ea) =>
{
if (newUser.ValidateNewUserInformation(newName, newEmail, newUsername, newPassword, ref errorMessage))
{
string activeDir = Environment.SystemDirectory.Substring(0, 1) + #":\Users\" + Environment.UserName + #"\My Documents\SSK\Users";
string newPath = System.IO.Path.Combine(activeDir, newUser.Username);
Directory.CreateDirectory(newPath);
newUser.SaveUserData(newUser);
newPath = System.IO.Path.Combine(activeDir, newUser.Username + #"\Settings");
Directory.CreateDirectory(newPath);
newUserSettings.SetDefaultValues();
newUserSettings.SaveSettings(newUser, newUserSettings);
validated = true;
}
else
ea.Cancel = true;
};
worker.RunWorkerCompleted += (o, ea) =>
{
if (validated)
{
IntelliMonitorWindow intelliMonitor = new IntelliMonitorWindow(newUser, newUserSettings);
intelliMonitor.Show();
this.Close();
}
validationProfile.IsBusy = false;
finishConfigButton.IsEnabled = true;
backToLoginHyperlink.IsEnabled = true;
errorLabel.Visibility = System.Windows.Visibility.Visible;
errorLabel.Content = errorMessage;
};
worker.RunWorkerAsync();
}
I am loading huge files to the memory but on this calculation my application is freezes .
Any idea what is the issue with my code ?
public void Drop(DragEventArgs args)
{
BackgroundWorker worker = new BackgroundWorker();
string fileName = IsSingleTextFile(args);
if (fileName == null) return;
worker.DoWork += (o, ea) =>
{
try
{
StreamReader fileToLoad = new StreamReader(fileName);
string filecontent = fileToLoad.ReadToEnd();
fileToLoad.Close();
// providing the information to the UI thread
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
new Action(() => SfmLogFile = filecontent));
}
catch (Exception)
{
throw;
}
};
worker.RunWorkerCompleted += (o, ea) =>
{
args.Handled = true;
IsBusy = false;
};
// Mark the event as handled, so TextBox's native Drop handler is not called.
IsBusy = true;
worker.RunWorkerAsync();
}
I'd transform your sample to something like this:
public void Drop(DragEventArgs args)
{
string fileName = IsSingleTextFile(args);
if (fileName == null) return;
// It is better to create worker after check for file name.
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (o, ea) =>
{
try
{
string filecontent = ReadAllText(fileName);
ea.Result = fileContent;
}
catch (Exception)
{
throw;
}
};
worker.RunWorkerCompleted += (o, ea) =>
{
var fileContent = ea.Result as string;
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
new Action(() => SfmLogFile = filecontent));
// if IsBusy propery is not on the UI thread, then you may leave it here
// otherwise it should be set using the dispatcher too.
IsBusy = false;
};
IsBusy = true;
worker.RunWorkerAsync();
// Mark the event as handled, so TextBox's native Drop handler is not called.
args.Handled = true;
}
I am not sure if it's the cause of your problem, but you are setting args.Handled in a callback for the background worker, so it will be called after the drop event handler has returned. It won't have the desired effect as it's set too late, and it might mess up something in the event handling.
Can I wait for a signal from an event so that, when I receive the signal then only I will proceed with next code segment?
For making it clear , I have the follwoing code:
hiddenMediaElement.Source = new Uri(strMediaFileName, UriKind.RelativeOrAbsolute);
hiddenMediaElement.MediaFailed += (obj, Sender) =>
{
bMediaError = true;
};
if (!bMediaError)
{
ObjChildMediaPlayer.Visibility = Visibility.Visible;
ObjChildMediaPlayer._currenTitle = strTitle;
ObjChildMediaPlayer.Show();
Content_FullScreenChanged(null, null);
}
The problem here is the if condition is executed before the MediaFailed event. But I want to wait for MediaFailed event to be executed 1st and then the if condition and I do not want to use events here.
How could I wait for the same? Can I use a mutex or something similar?
You can use AutoResetEvent to handle this situation. But I definitly would try to find another way if there is one.
var autoResetEvent = new AutoResetEvent(false);
hiddenMediaElement.Source = new Uri(strMediaFileName, UriKind.RelativeOrAbsolute);
hiddenMediaElement.MediaFailed += (obj, Sender) =>
{
bMediaError = true;
autoResetEvent.Set();
};
hiddenMediaElement.MediaOpened += (obj, Sender) =>
{
// I think this occurs when it is successfull. Else put it in the handler that handles a success
autoResetEvent.Set();
};
autoResetEvent.WaitOne(); // set a timeout value
if (!bMediaError)
{
ObjChildMediaPlayer.Visibility = Visibility.Visible;
ObjChildMediaPlayer._currenTitle = strTitle;
ObjChildMediaPlayer.Show();
Content_FullScreenChanged(null, null);
}
Or ... I'm not sure this will work, but try it out.
hiddenMediaElement.Source = new Uri(strMediaFileName, UriKind.RelativeOrAbsolute);
hiddenMediaElement.MediaOpened += (obj, sender) =>
{
ObjChildMediaPlayer.Visibility = Visibility.Visible;
ObjChildMediaPlayer._currenTitle = strTitle;
ObjChildMediaPlayer.Show();
Content_FullScreenChanged(null, null);
};
Put your code in the event handler:
hiddenMediaElement.Source = new Uri(strMediaFileName, UriKind.RelativeOrAbsolute);
hiddenMediaElement.MediaFailed += (obj, Sender) =>
{
ObjChildMediaPlayer.Visibility = Visibility.Visible;
ObjChildMediaPlayer._currenTitle = strTitle;
ObjChildMediaPlayer.Show();
Content_FullScreenChanged(null, null);
};