I am trying to show a progress bar when I click a button, and hide it when the work is finished. But when I show the progress bar in the button event handler, it doesn't work and is only shown after the work is done.
Here is my code:
private void Button_Click_1(object sender, RoutedEventArgs e)
{
this.Dispatcher.BeginInvoke((Action)(() =>
{
loadingprgoress.Visibility = Visibility.Visible;
}));
int usresult, psresult;
con.Open();
SqlDataReader data = null;
String myQueryEdit1 = #"SELECT Username, Password FROM [dbo].[Table]";
com.CommandText = myQueryEdit1;
com.Connection = con;
data = com.ExecuteReader();
Random rnd = new Random();
int newrnd = rnd.Next(1, 100);
if (data.Read())
{
string userhash = GenerateHashWithSalt(data["Username"].ToString(), newrnd.ToString());
string passhash = GenerateHashWithSalt(data["Password"].ToString(), newrnd.ToString());
string userhash1 = GenerateHashWithSalt("admin", newrnd.ToString());
string passhash1 = GenerateHashWithSalt(pasbox.Password, newrnd.ToString());
usresult = userhash.CompareTo(userhash1);
psresult = passhash.ToString().CompareTo(passhash1);
if (usresult == 0 && psresult == 0)
{
con.Close();
dental_main_Window neww = new dental_main_Window();
neww.Show();
Close();
}
else
{
con.Close();
pasbox.Password = "";
}
Thread.Sleep(3000);
this.Dispatcher.BeginInvoke((Action)(() =>
{
loadingprgoress.Visibility = Visibility.Hidden;
}));
return;
}
}
This happens because your UI thread is doing all the work (accessing the database) and until it's done, it can't process any more messages (like showing the progress bar).
You need to do the opposite, show the progress bar in the UI thread without BeginInvoke and do the database accesss in another thread. When you the other thread finishes, you need to do a BeginInvoke on the UI thread to hide the progress bar.
Here is another important note: this.Dispatcher.BeginInvoke doesn't spawn a thread. It "pushes" the command to the queue of the UI thread because all UI operations must be executed from the UI thread.
Here is what you do:
private void Button_Click_1(object sender, RoutedEventArgs e)
Show progress bar
Use Tasks to start your background process
Add a continuation task to your first task to send the message to the UI that the process has completed
In the continuation task use this.Dispatcher.BeginInvoke so that the UI staff happens on the UI thread.
Have a look at background worker (http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx)
It allows you to run an operation on a separate, dedicated thread.
During the background computation, you can raise the ProgressChanged event to notify the UI and update the ProgressBar accordingly:
private BackgroundWorker _worker;
public Form1(string[] args)
{
InitializeComponent();
_worker = new BackgroundWorker();
_worker.WorkerReportsProgress = true;
_worker.RunWorkerCompleted += worker_WorkCompleted;
_worker.DoWork += worker_DoWork;
_worker.ProgressChanged += worker_ProgressChanged;
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
DoStuff();
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
private void worker_WorkCompleted(object sender, RunWorkerCompletedEventArgs e)
{
_running = false;
UpdateUi();
}
private bool DoStuff()
{
//...
_worker.ReportProgress(20);
//...
_worker.ReportProgress(20);
return true;
}
private void btnUpdate_Click(object sender, EventArgs e)
{
_worker.RunWorkerAsync();
}
UI in WPF needs to execute on the UI thread. And you recognise that because I can see you using Dispatcher.BeginInvoke to interact with your progress window.
Your problem though is that you are executing your long-running task also on the UI thread. When you do that you don't give the progress window an opportunity to operate. The thread is consumed by the long-running task and is thus unable to service the UI.
The solution is to execute the long-running task away from the UI thread.
Related
I am using an MVVM model in my WPF application. I have an command binding to the cancel button. I have a start button which starts a few background workers. When i click on the cancel button, i want all the background workers to stop/quit.
With my current code when i click on cancel button, the background worker does not stop and the "StartEngineeringOperation" finishes. Can anyone please help me out with what i am doing wrong here?
Current code:
For EngineeringViewModel.cs:
public class EngineeringViewModel{
public EngineeringViewModel()
{
StartEngineering= new DelegateCommand(o =>
{
worker = new BackgroundWorker
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
worker.ProgressChanged += Worker_ProgressChanged;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
if (worker.IsBusy != true) worker.RunWorkerAsync();
worker.DoWork += (s, e) =>
{
StartEngineeringOperation();
if (worker.CancellationPending)
{
e.Cancel = true;
return;
}
};
},
k => true);
Cancel = new DelegateCommand(CancelEngineeringOperation);
}
private void StartEngineeringOperation()
{
startAlarmService();
startTrendQualityCheck();
}
private void CancelEngineeringOperation(object param)
{
worker.DoWork += (s, e) =>
{
if (worker.IsBusy)
{
worker.CancelAsync();
e.Cancel = true;
return;
}
};
}
}
I tried this :
but doesn't seem to work:
private void StartEngineeringOperation()
{
startAlarmService();
if (worker.CancellationPending)
{
e.Cancel = true;
return;
}
startTrendQualityCheck();
}
As you may have learned from te comments, you need to poll the state of the BackgroundWorker in your operations that you want to support cancellation. Then take measures to cancel the ongoing operation gracefully.
The example shows how to cancel a background thread on button click. The first example uses the old BackgroundWorker and the second the modern and cleaner Task library.
BackgroundWorker
private BackgroundWorker Worker { get; set; }
private void StartWorker()
{
this.Worker = new BackgroundWorker
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
this.Worker.DoWork += BackgroundWorker_DoWork;
}
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
DoCancellableWork();
// Stop BackgroundWorker from executing
if (worker.CancellationPending)
{
e.Cancel = true;
}
}
private void DoCancellableWork()
{
// Check for cancellation before executing the cancellable operation and allocating resources etc..
if (this.Worker.CancellationPending)
{
return;
}
// Periodically/regularly check for the cancellation flag
for (int i = 0; i <= 10000000000; i++)
{
if (this.Worker.CancellationPending)
{
// Cancel operation gracefully e.g., do some cleanup, free resources etc.
return;
}
// Do some work
}
}
// Alternatively use a command e.g., in a view model class
private void CancelBackgroundWorker_Click(object sender, EventArgs e)
{
if (this.Worker.WorkerSupportsCancellation)
{
this.Worker.CancelAsync();
}
}
Task library
The example uses Progress<T> to report progress from the background thread to the UI thread.
private CancellationTokenSource CancellationTokenSource { get; set; }
private async Task StartWorker()
{
this.CancellationTokenSource = new CancellationTokenSource();
// Prepare callback to update UI from the background thread.
// The Progress<T> instance MUST be created on the UI thread
IProgress<int> progressReporter = new Progress<int>(progress => this.ProgressBar.Value = progress);
await Task.Run(
() => DoWork(progressReporter, this.CancellationTokenSource.Token),
this.CancellationTokenSource.Token);
this.CancellationTokenSource.Dispose();
}
private void DoWork(IProgress<int> progressReporter, CancellationToken cancellationToken)
{
DoCancellableWork(progressReporter, cancellationToken);
}
private void DoCancellableWork(IProgress<int> progressReporter, CancellationToken cancellationToken)
{
// Check for cancellation before executing the operation and allocating resources etc..
if (cancellationToken.IsCancellationRequested)
{
return;
}
// Periodically/regularly check for the cancellation flag
for (int i = 0; i <= 10000000000; i++)
{
if (cancellationToken.IsCancellationRequested)
{
// Cancel operation gracefully e.g., do some cleanup, free resources etc.
return;
}
// Do some work
// Report progress
progressReporter.Report(20);
}
}
// Alternatively use a command e.g., in a view model class
private void CancelBackgroundThread_Click(object sender, EventArgs e)
{
this.CancellationtokenSource?.Cancel();
}
Since the OP describes the task being done as "checking services", I would assume the work done looks something like this:
while(true){
// check service
// Post result back to UI thread
Thread.Sleep(...);
}
This is not the best way to write such such a check. As in most cases where Thread.Sleep is used, a timer would be a better alternative:
var myTimer = new System.Timers.Timer(...);
myTimer .Elapsed += OnTimedEvent;
myTimer .AutoReset = true;
myTimer .Enabled = true;
...
private void OnTimedEvent(Object source, ElapsedEventArgs e)
{
// check service
// Post result back to UI thread
}
This makes the problem of stopping/starting the task being done a simple matter of changing the Enabled-flag of the timer. It is also possible to use a timer, or a synchronization context to run the event directly on the UI thread, this is probably the best solution if "checking services" only takes a few ms.
On button_click event I have a query that will take lengthy time. So i run it on BackgroundWorker
private void btnGenerate_Click(object sender, EventArgs e)
{
btnGenerate.Enabled = false;
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate (object s, DoWorkEventArgs args)
{
Data = DataLoader.GetData(Environment.UserName); // stored procedure execution
if (Data != null)
{
GenerateExcel(Data);
GenerateSingleExcel(Data);
}
};
worker.RunWorkerCompleted += delegate (object s, RunWorkerCompletedEventArgs args)
{
progressBar1.Visible = false;// ProgressBarStyle.Marquee
btnGenerate.Enabled = true;
};
worker.RunWorkerAsync();
}
My problem is, I need to set
btnGenerate.Enable=false;
at the button_click. and enable after the execution done.
I tried it inside RunWorkerCompleted but it's showing
'Cross-thread operation not valid: Control 'btnGenerate' accessed from a thread other than the thread it was created on.'
Any suggestion will be helpful.
Your main problem is that the events of BackgroundWorker are executed on the worker's thread, not the UI thread. But UI elements should only be accessed from the UI thread.
To solve it I suggest to use async/await instead of a BackgroundWorker:
// declare as async
private async void btnGenerate_Click(object sender, EventArgs e)
{
btnGenerate.Enabled = false;
Data = await Task.Run(() => {
var data = DataLoader.GetData(Environment.UserName); // stored procedure execution
if (data != null)
{
GenerateExcel(Data);
GenerateSingleExcel(Data);
}
return data; // as suggested by Vlad, don't set Data on this thread
});
// this is now executed back on the UI thread
progressBar1.Visible = false;// ProgressBarStyle.Marquee
btnGenerate.Enabled = true;
}
It would even be preferable if DataLoader provided an asynchronous GetDataAsync, so you wouldn't need the Task.Run().
If async is not possible (for whatever reason), your RunWorkerCompleted handler should use Invoke or BeginInvoke:
worker.RunWorkerCompleted += OnRunWorkerCompleted;
//...
public void OnRunWorkerCompleted(object s, RunWorkerCompletedEventArgs args)
{
if (InvokeRequired)
{
// not on the UI thread - use (Begin-)Invoke
BeginInvoke(new RunWorkerCompletedEventHandler(OnRunWorkerCompleted), s, args);
return;
}
// now we're on the UI thread
progressBar1.Visible = false;// ProgressBarStyle.Marquee
btnGenerate.Enabled = true;
}
first off I'd like to say I'm brand new to C# so I am not too aware with how the background worker is supposed to be implemented. I have a GUI program that basically pings a domain a returns the response to a textbox. I am able to get it to work normally, however, it freezes the code because it is running on the same thread which is why I am trying to implement a background worker.
Here is the basic setup
private void button1_Click(object sender, EventArgs e)
{
url = textBox1.Text;
button1.Enabled = false;
button2.Enabled = true;
bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
bgWorker.RunWorkerAsync();
}
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
do
{
if (bgWorker.CancellationPending)
break;
Invoke((MethodInvoker)delegate { monitor(); });
} while (true);
}
public void monitor()
{
textBox2.AppendText("Status of: " + url + "\n");
Status(url);
System.Threading.Thread.Sleep(30000);
}
private void Status(string url)
{
// This method does all the ping work and also appends the status to the Text box as it goes through , as OK or down
}
I have not worked with bgworkers before and as you can imagine it's confusing. I've looked at tons of other articles and I can't seem to get it. Sorry if the code looks crazy, I'm trying to learn.
Use Microsoft's Reactive Framework (NuGet "System.Reactive.Windows.Forms" and add using System.Reactive.Linq;) and then you can do this:
private void button1_Click(object sender, EventArgs e)
{
var url = textBox1.Text;
Observable
.Interval(TimeSpan.FromMinutes(0.5))
.SelectMany(_ => Observable.Start(() => Status(url)))
.ObserveOn(this)
.Subscribe(status => textBox2.AppendText("Status of: " + status + "\n"));
}
You then just need to change Status to have this signature: string Status(string url).
That's it. No background worker. No invoking. And Status is nicely run on a background thread.
You've got several mistakes. First,
Invoke((MethodInvoker)delegate
{
monitor();
});
will call monitor() on your UI thread. In almost all cases you should not call methods on other threads. You especially should not call methods that block or do anything that takes more than a few milliseconds on your UI thread, and that is what this does:
System.Threading.Thread.Sleep(30000);
Instead of calling a method on another thread; submit immutable data to the other thread and let the thread decide when to handle it. There is an event already built in to BackgroundWorker which does that. Before you call bgWorker.RunWorkerAsync() do this:
url = new Uri(something);
bgWorker.WorkerReportsProgress = true;
bgWorker.WorkerSupportsCancellation = true;
bgWorker.ProgressChanged += Bgw_ProgressChanged;
private void Bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
textBox2.AppendText("Status of: " + url + ": " + e.UserState.ToString()
+ Environment.NewLine);
}
Your bgWorker_DoWork should look more like this:
void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
while (!bgw.CancellationPending)
{
System.Threading.Thread.Sleep(new TimeSpan(0, 0, 30));
var status = ResultOfPing(e.Argument as Uri);
bgw.ReportProgress(0, status);
}
e.Cancel = true;
}
and you should call it like this:
bgWorker.RunWorkerAsync(url);
You've got a second problem. BackgroundWorker creates a thread, and your thread is going to spend most of its time blocked on a timer or waiting for network responses. That is a poor use of a thread. You would be better off using completion callbacks or async/await.
The background worker is running on a thread pool thread, but your call to Status and Sleep is running on the UI thread. You need to move that stuff back into bgWorker_DoWork.
Try this code:
public partial class Form1 : Form
{
bool cancel;
public Form1()
{
InitializeComponent();
}
public void StartPinging()
{
this.cancel = false;
startButton.Enabled = false;
stopButton.Enabled = true;
responseBox.Clear();
responseBox.AppendText("Starting to ping server.");
responseBox.AppendText(Environment.NewLine);
var bw = new BackgroundWorker
{
WorkerReportsProgress = false,
WorkerSupportsCancellation = true
};
bw.DoWork += (obj, ev) =>
{
while (!cancel)
{
// Ping Server Here
string response = Server.PingServer();
this.Invoke(new UiMethod(() =>
{
responseBox.AppendText(response);
responseBox.AppendText(Environment.NewLine);
}));
}
};
bw.RunWorkerCompleted += (obj, ev) =>
{
this.Invoke(new UiMethod(() =>
{
responseBox.AppendText("Stopped pinging the server.");
responseBox.AppendText(Environment.NewLine);
startButton.Enabled = true;
stopButton.Enabled = false;
}));
};
bw.RunWorkerAsync();
}
delegate void UiMethod();
private void startButton_Click(object sender, EventArgs e)
{
StartPinging();
}
private void stopButton_Click(object sender, EventArgs e)
{
responseBox.AppendText("Cancelation Pressed.");
responseBox.AppendText(Environment.NewLine);
cancel = true;
}
}
public class Server
{
static Random rng = new Random();
public static string PingServer()
{
int time = 1200 + rng.Next(2400);
Thread.Sleep(time);
return $"{time} ms";
}
}
Erwin, when dealing with C# - threads and UI elements usually you will come across cross-thread operations i.e. Background thread with UI threads. This interaction needs to be done in thread safe way with the help of Invoke to avoid invalid operations.
Please look into below resource: InvokeRequired section.
https://learn.microsoft.com/en-us/dotnet/framework/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls
I want to set a progress bar for my application.
I am using a background to download somefiles from my server.
It would be nice if I set a progress bar for that.
I know to use
ProgressBar.Value = 5;
and more.
but I want to use progress bar.'
I am using this code for progress bar
private void bgDownload_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.downloadProgressBar.Value = e.ProgressPercentage;
}
but its not working.
Can any one say how can I do it.
Thanks In Advance
If you're doing processing in the background, the UI will not update until its over.
So what you'll need to do is create a seperate thread which the processing will happen in, and within that thread update the progress bar. In the main thread don't do anything.
To update the progress bar from within another thread, you will need a delegate (or you'll get a runtime error).
Hope this can get you started.
You can try to use BackgroundWorker to download some files.
So backgroud download won't effect your Process Bar.
private void btnDownLoad_Click(System.Object sender,
System.EventArgs e)
{
// Start the asynchronous operation.
backgroundWorkerDownLoadFile.RunWorkerAsync();
}
private void backgroundWorkerDownLoadFile_DoWork(object sender,
DoWorkEventArgs e)
{
// Get the File in Server.
}
I assume you are using background worker component. So, you will have to call ReportProgress method of backgroundworker from its DoWork method which should update the progress bar. Please find below some reference on ReportProgress method from MSDN:
http://msdn.microsoft.com/en-us/library/ka89zff4.aspx
in addition to what JKhuang wrote, to alter the values in your progress bar you'll need to add
a delegate and a callback function
delegate void SetProgCallback(int newVal);
private void SetProgressbarValue(int newVal)
{
if (progbar.InvokeRequired)
{
SetProgCallback d = SetProgressbarValue;
Invoke(d, new object[] { newVal });
}
else
{
//insert your actual code here
}
}
Here is an example of how to do this.
public int ProgressBarPercentage { get; set; }
public string StatusMessage { get; set; }
public void StartDownload()
{
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.RunWorkerAsync();
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Run clean up code here once complete (ie make sure progress bar is at 100 percent....
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
// Download files here
List<string> filestoget = new List<string>();
filestoget.Add("File1");
filestoget.Add("File2");
filestoget.Add("File3");
filestoget.Add("File4");
filestoget.Add("File5");
foreach (string file in filestoget)
{
// Get File
// Report output
int progress = 0; // add soemthing here to calculate your progress
bw.ReportProgress(progress, string.Format("File {0} downloaded", file));
}
}
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
ProgressBarPercentage = e.ProgressPercentage;
StatusMessage = e.UserState.ToString();
}
In my application I need to perform a series of initialization steps, these take 7-8 seconds to complete during which my UI becomes unresponsive. To resolve this I perform the initialization in a separate thread:
public void Initialization()
{
Thread initThread = new Thread(new ThreadStart(InitializationThread));
initThread.Start();
}
public void InitializationThread()
{
outputMessage("Initializing...");
//DO INITIALIZATION
outputMessage("Initialization Complete");
}
I have read a few articles about the BackgroundWorker and how it should allow me to keep my application responsive without ever having to write a thread to perform lengthy tasks but I haven't had any success trying to implement it, could anyone tell how I would do this using the BackgroundWorker?
Add using
using System.ComponentModel;
Declare Background Worker:
private readonly BackgroundWorker worker = new BackgroundWorker();
Subscribe to events:
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
Implement two methods:
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
// run all background tasks here
}
private void worker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
//update ui once worker complete his work
}
Run worker async whenever your need.
worker.RunWorkerAsync();
Track progress (optional, but often useful)
a) subscribe to ProgressChanged event and use ReportProgress(Int32) in DoWork
b) set worker.WorkerReportsProgress = true; (credits to #zagy)
You may want to also look into using Task instead of background workers.
The easiest way to do this is in your example is Task.Run(InitializationThread);.
There are several benefits to using tasks instead of background workers. For example, the new async/await features in .net 4.5 use Task for threading. Here is some documentation about Task
https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task
using System;
using System.ComponentModel;
using System.Threading;
namespace BackGroundWorkerExample
{
class Program
{
private static BackgroundWorker backgroundWorker;
static void Main(string[] args)
{
backgroundWorker = new BackgroundWorker
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
backgroundWorker.DoWork += backgroundWorker_DoWork;
//For the display of operation progress to UI.
backgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged;
//After the completation of operation.
backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
backgroundWorker.RunWorkerAsync("Press Enter in the next 5 seconds to Cancel operation:");
Console.ReadLine();
if (backgroundWorker.IsBusy)
{
backgroundWorker.CancelAsync();
Console.ReadLine();
}
}
static void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 200; i++)
{
if (backgroundWorker.CancellationPending)
{
e.Cancel = true;
return;
}
backgroundWorker.ReportProgress(i);
Thread.Sleep(1000);
e.Result = 1000;
}
}
static void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Console.WriteLine("Completed" + e.ProgressPercentage + "%");
}
static void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
Console.WriteLine("Operation Cancelled");
}
else if (e.Error != null)
{
Console.WriteLine("Error in Process :" + e.Error);
}
else
{
Console.WriteLine("Operation Completed :" + e.Result);
}
}
}
}
Also, referr the below link you will understand the concepts of Background:
http://www.c-sharpcorner.com/UploadFile/1c8574/threads-in-wpf/
I found this (WPF Multithreading: Using the BackgroundWorker and Reporting the Progress to the UI. link) to contain the rest of the details which are missing from #Andrew's answer.
The one thing I found very useful was that the worker thread couldn't access the MainWindow's controls (in it's own method), however when using a delegate inside the main windows event handler it was possible.
worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
{
pd.Close();
// Get a result from the asynchronous worker
T t = (t)args.Result
this.ExampleControl.Text = t.BlaBla;
};