Please bear with my ignorance on this subject and on the terminology I have used. Please correct me where I am incorrect.
I have used a background Worker from the toolbox onto my form and I am passing a method to the DoWork event. What I have understood in this first attempt with background Worker is that I can use the background Worker that I've created only for 1 task. See code below:
private void btn1_Click(object sender, EventArgs e)
{
// Should call the uploadToDB1 using BackgroundWorker's DoWork event.
backgroundWorker1.RunWorkerAsync();
}
private void btn2_Click(object sender, EventArgs e)
{
// Should call the uploadToDB2 using BackgroundWorker's DoWork event.
backgroundWorker1.RunWorkerAsync();
}
private void uploadToDB1()
{
// Code for uploading to DB 1.
}
private void uploadToDB2()
{
// Code for uploading to DB 2.
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
try
{
uploadToDB1(); // I want to change this to uploadToDB2 i.e. a variable method, How do I assign a call to this?
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Visible = true;
progressBar1.Maximum = maxRecords;
lblProgress.Text = Convert.ToString(e.ProgressPercentage.ToString() + "/" + maxRecords);
progressBar1.Value = e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressBar1.Visible = false;
lblProgress.Text = "";
}
I need to be able to dynamically pass a method to the DoWork event without having the need to create multiple background Workers as the actions in the rest of the events related to the background Worker remains unchanged.
Could you please advise how I should go about doing this?
Updated Code using TPL, however I am getting a cross thread error. Could you please help with a corrected code? Upload to DB 2 should happen only after upload to DB 1 is complete. So each time an upload happens the label and progress bar needs to be updated. I also need to pass different text to the label.
private void btn1_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(uploadToDB1);
}
private void uploadToDB1()
{
for(i=0;i<dt.rows.count-1;i++)
{
// Code for inserting into DB1.
progressbar1.maximum=dt.rows.count-1;
progressbar1.value=i;
}
uploadToDB2();
}
private void uploadToDB2()
{
for(i=0;i<dt.rows.count-1;i++)
{
// Code for inserting into DB2.
progressbar1.maximum=dt.rows.count-1;
progressbar1.value=i;
}
}
What you can do, and is a bit of a hack, is pass an Action as an argument for invocation to your DoWorkAsync:
var bw = new BackgroundWorker();
bw.DoWork += (s, o) =>
{
Action actualWork = (Action)o.Argument;
actualWork();
}
and when you invoke DoWorkAsync:
Action action = () => DoSomething();
bw.RunWorkerAsync(action);
Instead, as #Sriram suggested, look into the Task Parallel Library, which will make you life a bit easier:
private async void btn1_Click(object sender, EventArgs e)
{
await Task.Run(UpdateFirst);
// Update UI here.
}
private async void btn2_Click(object sender, EventArgs e)
{
await Task.Run(UpdateSecond);
// Update UI again.
}
An extensive answer on TPL and the use of IProgess<T> can be found in How to provide a feedback to UI in a async method?
Related
I know that this question has posted several times, but this situation is different. So supposed that I'm executing a method that need to iterate through several items (database rows), this require a lot of time.
Now in my BackgroundWorker I need in some case to stop the synchronization, in particular when the user press a button. What I did in the _DoWork event is this:
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
while (!worker.CancellationPending)
{
LongOperation();
}
}
now the problem's that when I call worker.CancelAsync() the LongOperation() continue the execution but shouldn't! 'cause the while have the condition of CancellationPending. I saw in the net that this solution is thread-safe, so maybe am I doing something wrong?
All you need is the following structure
private void runButton_Click(object sender, EventArgs e)
{
worker=new BackgroundWorker();
worker.WorkerSupportsCancellation=true;
worker.RunWorkerCompleted+=Bk_RunWorkerCompleted;
worker.DoWork+=Bk_DoWork;
worker.RunWorkerAsync();
}
private void cancelButton_Click(object sender, EventArgs e)
{
worker.CancelAsync();
}
void ReallyReallyLongOperation(BackgroundWorker worker)
{
...within a loop
if(worker.CancellationPending)
{
return;
}
}
private void Bk_DoWork(object sender, DoWorkEventArgs e)
{
ReallyReallyLongOperation(worker);
if(worker.CancellationPending)
{
e.Cancel = true;
}
}
private void Bk_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if(!e.Cancelled)
{
...
}
}
the LongOperation() continue the execution but shouldn't! 'cause the while have the condition of CancellationPending.
No, it should continue execution! You are completely wrong with understanding of the while check. It does not check every second for the cancellation, it does the check only before starting the LongOperation!
So the only thing you can do in such situation is to check the worker.CancellationPending property inside the LongOperation method, not outside of it.
I'm working on a utility to copy a directory to multiple USB sticks. When Form1 loads, I would like for a label to display status "Detecting Disk Drives...", and then call a method to read the drives and populate the form with information. I have it working, except when the form loads it calls the method before displaying the label. Therefore it appears to be hung (the label is actually a white box on a gray background). I have tried timers and threads and everything I can think of, each with a different dead end. I have not yet found a way to have the label update before calling the method to read the drives.
The method getAndDisplayData() is wait 'hangs' my program. I would like for it not to be called until after the form has updated the text of lblDisplayStatus.Text
I also do not want the user to have to interact with the form before calling the method.
Here is my C# code:
private void USB_Utility_Load(object sender, EventArgs e)
{
lblDisplayStatus.Text = "Detecting Disk Drives...";
}
private void tabUSB_Prep_Enter(object sender, EventArgs e)
{
tabUSB_Prep.Controls.Clear();
getAndDisplayData();
}
Any help would be greatly appreciated.
Here is the code that I ended up with:
BackgroundWorker _worker;
private void USB_Utility_Load(object sender, EventArgs e)
{
_worker = new BackgroundWorker(); // Should be a field on the form.
_worker.DoWork += DoWork;
_worker.RunWorkerCompleted += RunWorkerCompleted;
lblDisplayStatus.Text = "Detecting Disk Drives...";
_worker.RunWorkerAsync();
}
//Background Worker
private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
lblDisplayStatus.Text = "Done...";
displayData();
}
private void DoWork(object sender, DoWorkEventArgs e)
{
getData();
}
The old-fashioned way would be to use a BackgroundWorker to run the blocking work in getAndDisplayData and then update the label before starting the worker and again when the worker completes.
Now-adays I assume you could also use tasks to get the exact same result, but I haven't actually tried it as WinForms is not often first choice for new projects.
BackgroundWorker _worker;
public void Form_Load(object sender, EventArgs e) {
_worker = new BackgroundWorker(); // Should be a field on the form.
_worker.DoWork += DoWork;
_worker.RunWorkerCompleted += RunWorkerCompleted;
lblDisplayStatus.Text = "Detecting Disk Drives...";
_worker.RunWorkerAsync();
}
private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
lblDisplayStatus.Text = "Done...";
}
private void DoWork(object sender, DoWorkEventArgs e) {
getAndDisplayData();
}
About background workers
you can try this
private void USB_Utility_Load(object sender, EventArgs e)
{
lblDisplayStatus.Text = "Detecting Disk Drives...";
}
private void tabUSB_Prep_Enter(object sender, EventArgs e)
{
tabUSB_Prep.Controls.Clear();
Task<List<string>> t = new Task<List<string>>(DetectDrivesMethod());
t.ContinueWith((result)=>DisplayDrives(result.Result),TaskScheduler.FromCurrentSynchronizationContext);
t.Start();
}
You can tweak the code to fit your requirement. In DetectDriveMethod you will have logic to get data in background thread and in continue with, you can have logic to update UI. It is important that you pass syncronization context otherwise you will end up with Cross Thread exceptions.
If you want to use the new(ish) async/await pattern, you need to use the TaskScheduler to update the UI from the original thread. Here's an example:
// clear the form
tabUSB_Prep.Controls.Clear();
// This is just to show crossing a "context" works
string test = "";
// get the UI's current TaskScheduler
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
// This can be used to wrap a method that doesn't
// directly implement async/await
Task.Run(() =>
{
// Your method to GET the data (don't update the UI here)
test = "I can set a variable in this context!";
}).ContinueWith(task =>
{
if (task.Status == TaskStatus.RanToCompletion)
{
// update your UI here
// Again, this is just to show how crossing the context works
MessageBox.Show(test);
}
else
{
// update UI with an error message, or display a MessageBox?
}
}, scheduler);
I have service call in my form application. When my button has been clicked I am calling my service method. Here is my code block:
void listBox_SelectedValueChanged(object sender, EventArgs e)
{
if (listBox.SelectedItem.Equals("Demo"))
{
progressBar1.Visible = true;
backgroundWorker1.RunWorkerAsync();
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
ObservableCollection<FilterInfo> filterInfos = new ObservableCollection<FilterInfo>();
FilterInfo myFilterObj = new FilterInfo("SUPERVISOR", userName);
filterInfos.Add(myFilterObj);
ObservableCollection<DEMOBec> demos = demoSvc.GetDemoByFilter(filterInfos, false);
dt = new Converter<DEMOBec>().ConvertDataTable(demos.ToList());
}
Before calling the service, I make my ProgressBar (Style = Marquee) visible. But I couldn't make it invisible in completed method because of Cross Thread problem.
When I tried to do something with in UI thread in BGWorker's Completed event,
void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressBar1.Visible = false;
}
I am getting an exception :
Cross-thread operation not valid: Control 'Form1' accessed from a thread other than the thread it was created on.
How can I handle this problem?
You need to use Invoke() method
private delegate void ToDoDelegate();
void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Invoke(new ToDoDelegate(() => progressBar1.Visible = false));
}
more info for Invoke()
http://msdn.microsoft.com/de-de/library/System.Windows.Forms.Control.Invoke(v=vs.110).aspx
Here's alittle piece of code that I always love to use, I don't remember where I got it from I put it in my Dropbox a long time ago.
public static class ControlExtensions
{
public static TResult InvokeEx<TControl, TResult>(this TControl control,
Func<TControl, TResult> func)
where TControl : Control
{
return control.InvokeRequired
? (TResult)control.Invoke(func, control)
: func(control);
}
public static void InvokeEx<TControl>(this TControl control,
Action<TControl> func)
where TControl : Control
{
control.InvokeEx(c => { func(c); return c; });
}
public static void InvokeEx<TControl>(this TControl control, Action action)
where TControl : Control
{
control.InvokeEx(c => action());
}
}
Usage
this.InvokeEx( x => Progressbar1.Visible = false); //Note 'x' could be any variable
This way you won't have to type all that out each time, and it's so simple to use. Just add this right to the end of your form class (after the last bracket closes). You can use this to preform any cross thread operations safely.
Modify the Visibility of the ProgressBar on the UI thread.
Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)
delegate()
{
progressBar1.Visible = false;
});
I think the exception isn't raised in the RunWorkerCompleted event (as in comments said). My suggestion is, that the lines you try to access UI cmponents in the DoWork event fires it.
Try to put the data you need in the DoWork event into DoWorkEventArgs e. Tons of examples how to do it are provided on SO like this discussion.
When the work has finished, provide the "main UI thread" the modified/new data by using event args, too, like this:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
// do some work
e.Result = ... ; // modified/new data
}
and retrieve it in the following way:
void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
var newData = (CastToWhatever) e.Result;
}
Hope it helps =)
EDIT
Your code in backgroundWorker1_RunWorkerCompleted() is definitly not the source of the exception. It built a little and simple example to demonstrate my argment:
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync(textBox1.Text);
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
var num = Int32.Parse(e.Argument.ToString()) + 1;
backgroundWorker1.ReportProgress(num);
e.Result = num;
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = (int)e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
textBox1.Text = e.Result.ToString();
}
What it does? On the WinForm there is a TextBox with a number, a ProgressBar and a Button to start a BackGroundWorker.
The worker gets the number from the TextBox (UI thread), DoWork increases it by 1 and saves it as result (new thread). Additionally it reports the increased number to the ProgressBar (cross-thread).
Finally RunWorkerCompleted reads out the result and stores the new value in the TextBox (UI thread).
So, please re-check your code ;-)
I am making and app using C# and Winforms that archives and saves folders to specified locations,for archiving folders i have a BackgroundWorker which takes as input a folder path and generates a zip archive.Now in the next step the file needs to be moved at specified location,again as the file is large enough and could hang up UI thread i moved the code to another BackgroundWorker named FileMove,everything works well except that the FileMove is not reporting any progress,here is the function that i call as soon as archiving is over;
private void FileMove_DoWork(object sender, DoWorkEventArgs e)
{
label3.Text = "Saving file,please wait...";
File.Move(temppath + #"\Output.jpg", savefilename);
}
private void FileMove_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label3.Text = "Saving file,please wait... " + e.ProgressPercentage.ToString(); //This should show Progress Percentage but it doesn't.
}
private void FileMove_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
label3.Text = ("The folder has been successfully hidden.");
button1.Enabled = true;
button2.Enabled = true;
button3.Enabled = true;
this.ControlBox = true;
}
The problem i'm facing is as soon as file moving starts label3 shows "Saving file,please wait..." and after a long time(as i'm compressing 900-1000 MB)it shows "The folder has been successfully hidden.".During ProgressChanged event label should also show Percentage but it doesn't.Please point out or correct where i've gone wrong.Any help will be appreciated.
First, your BackgroundWorker is trying to update the UI from its background thread, which is a no-no in multithreaded UI apps.
To update the UI you'll need it to switch to the UI thread to do the update. This is done by first checking if the label3.InvokeRequired is true (indicating you can't update the UI from the current thread), then passing a delegate to label3.Invoke (not Delegate.Invoke())
This is a pattern you need to be very familiar with for WinForms development. The Control.Invoke MSDN page includes a sample of leveraging this pattern.
Second, you need to call BackgroundWorker.ReportProgress() periodically, which fires the ProgressChanged event with a percentage-complete value.
Here's a sample app. It assumes you've got a Form with a Button, a Label, and a BackgroundWorker with WorkgerReportsProgress = true (and WorkerSupportsCancellation = true, for bonus points).
When you click the button it starts a 5-second blocking task, then switches to 10 1-second blocking tasks (reporting progress along the way).
Note the helper method InvokeIfRequired() that ensures the UI is updated from the correct thread. There's no harm in using this excessively.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if (backgroundWorker1.IsBusy)
{
label1.Text = "Reset!";
backgroundWorker1.CancelAsync();
return;
}
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
label1.InvokeIfRequired(() => label1.Text = "Gettin busy");
System.Threading.Thread.Sleep(5000);
for (int i = 0; i < 10; i++)
{
backgroundWorker1.ReportProgress(i*10);
System.Threading.Thread.Sleep(1000);
}
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
label1.InvokeIfRequired(() => label1.Text = "Done");
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.InvokeIfRequired(() => label1.Text = string.Format("{0}% done", e.ProgressPercentage));
}
}
public static class InvokeExtensions
{
public static void InvokeIfRequired(this ISynchronizeInvoke control, MethodInvoker action)
{
if (control.InvokeRequired)
{
control.Invoke(action, null);
}
else
{
action();
}
}
}
I am using Windows Form application for my thread demo. When I click on button1 ,It will start the thread and recursively doing a work.
Here the Form will not hang as I expected. I want to Stop the currently running thread when I click on Button2. However this won't work.
private void button1_Click(object sender, EventArgs e)
{
t = new Thread(doWork); // Kick off a new thread
t.Start();
}
private void button2_Click(object sender, EventArgs e)
{
t.Abort();
}
static void doWork()
{
while (true)
{
//My work
}
}
}
.When Im debugging, the button2_Click method won't hit the pointer. I think because Thread is keep busy.
Please correct me if I going wrong somewhere.
You can't kill thread like this. The reason is to avoid situations where you add lock in thread and then kill it before lock is released.
You can create global variable and control your thread using it.
Simple sample:
private volatile bool m_StopThread;
private void button1_Click(object sender, EventArgs e)
{
t = new Thread(doWork); // Kick off a new thread
t.Start();
}
private void button2_Click(object sender, EventArgs e)
{
m_StopThread = true;
}
static void doWork()
{
while (!m_StopThread)
{
//My work
}
}