I'm working on my first WPF project and I'm trying to add a progress bar for a lengthy function.
I have added message boxes to notify me on function success/error. I need to use IsIndeterminate type progress bar.
The RunWorkerAsync() line also gets called properly but then when the DoWork function call the lengthy function inside it, the animation doesn't work.When the function is over and the message-box pops up the animation works fine.
private void ButtonPipeline_Click(object sender, RoutedEventArgs e)
{
pbStatus.IsIndeterminate = true;
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.DoWork += worker_DoWorkPipeline_Click;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.RunWorkerAsync();
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
MessageBox.Show("Sync Interrupted.", "Message", MessageBoxButton.OK, MessageBoxImage.Information);
}
else
{
pbStatus.IsIndeterminate = false;
MessageBox.Show("Synced Completed.", "Sync Complete", MessageBoxButton.OK, MessageBoxImage.None);
pbStatus.Value = 0;
}
}
void worker_DoWorkPipeline_Click(object sender, DoWorkEventArgs e)
{
this.Dispatcher.Invoke(() =>
{
var worker = sender as BackgroundWorker;
try
{
var pipeline = GetPipeline(); //THis function throws "The calling thread cannot access this object because a different thread owns it"
if (pipeline.Name.Equals("<None>"))
{
MessageBox.Show("Please select a pipeline.", "Missing Data", MessageBoxButton.OK, MessageBoxImage.Warning);
worker.CancelAsync();
}
else
{
aLongFunction(pipeline);
MessageBox.Show("Pipeline: " + pipeline + Environment.NewLine + "Successfully Synced.", "Sync Complete", MessageBoxButton.OK, MessageBoxImage.None);
}
if (worker.CancellationPending == true)
{
pbStatus.IsIndeterminate = false;
pbStatus.Value = 0;
e.Cancel = true;
return;
}
}
catch (Exception ex)
{
worker.CancelAsync();
MessageBox.Show(ex.InnerException.ToString(), "Exception Occoured!", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
}
private void aLongFunction(Pipeline pipeline)
{
var session = new SynchronizationSession(pipeline);
session.Run();
MessageBox.Show("Successfully Synced.", "Sync Complete", MessageBoxButton.OK, MessageBoxImage.None);
}
public void Run()
{
anotherFunction();
}
private Pipeline GetPipeline()
{
var pipeline = (Pipeline)DropdownSyncPipeline.SelectedItem; //This throws and error since trying to access another UI Object.
if (null != pipeline)
{
if (0 == pipeline.Id)
{
var p = PerfOtherPipeline.Text;
if (!string.IsNullOrEmpty(p)) pipeline = BrokerDataCache.Pipelines.Find(p_ => p.Equals(p_.Name));
}
}
return pipeline;
}
BackgroundWorker is a tool to help abstract the handling of threads, but the code you have posted uses it in an odd way:
The event attached to it at DoWork (worker_DoWorkPipeline_Click) will run in a different thread than the UI, however since you have the entire method wrapped in an invoke to Dispatcher, all of aLongFunction will run on the same thread as the UI.
From your description of the error, the problem is that the progress bar stops animating, right? The reason for this is that the UI is not responsive whenever code is run on its thread using Dispatcher.Invoke.
The solution to this issue will be to remove the Invoke method from the worker_DoWorkPipeline_Click.
However if the code in SynchronizationSession accesses any UI objects in your project, you can expect an exception to occur for this since WPF UI objects generally can only be accessed on the same thread. You haven't provided the code for the function so we cannot tell from what you have provided.
If the SynchronizationSession, or anything else running within the Backgroundworker needs to update the UI, use Dispatcher.Invoke only for the part of the code that needs to update the UI. This will avoid blocking the UI for the entirety of the operation but only do so when necessary.
Related
I've got one form called Sorter. There is the button 'jademy' on it which opens window 'Progress Window'
private void jademy_Click(object sender, EventArgs e)
{
ProgressWindow progress = new ProgressWindow();
progress.ShowDialog();
}
Code of 'Progress Window' form is following:
public partial class ProgressWindow : Form
{
private BackgroundWorker backgroundWorker = new BackgroundWorker();
public ProgressWindow()
{
InitializeComponent();
stop.Visible = true;
ok.Visible = false;
backgroundWorker.RunWorkerAsync();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.WorkerSupportsCancellation = true;
#region block1
backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
#endregion
}
private void stop_Click(object sender, EventArgs e)
{
backgroundWorker.CancelAsync();
}
private void ok_Click(object sender, EventArgs e)
{
this.Close();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(100);
backgroundWorker.ReportProgress(i);
}
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
this.Text = "Done: " + e.ProgressPercentage.ToString() + "%";
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{
MessageBox.Show("Cancelled", "Message", MessageBoxButtons.OKCancel, MessageBoxIcon.Asterisk);
}
else if (!(e.Error == null))
{
MessageBox.Show("Error: " + e.Error.Message, "ERROR!", MessageBoxButtons.OKCancel);
}
else
{
ok.Visible = true;
stop.Visible = false;
}
}
}
Now. I have three problems.
Click on stop button does nothing. It seems that 'backgroundWorker.CancelAsync()' doesn't stop the process.
When I close progress window and I want to run it again I have to wait some time before click on 'jademy' button. Otherwise progress window is displayed like this:
(and nothing changes) instead of this: It looks like the program "remembers" that work was done even though it is a new instance of ProgressWindow. Notice that on the incorrect version 'OK' button is visible at once - instead of waiting for the completion of the work.
I would like to clarify the code in "block 1". To be honest I don't understand it fully. Is this part really essential or not? I mean, I've found a lot of examples (also on this forum - e.g. here), where this part wasn't included and users were reporting that the solution works. In my case, without this part progress bar didn't work at all, but maybe I've done something wrong.
Calling CancelAsync stops any pending work. But if the work has already started, the method body needs to check if cancel was called. See CancelAsync
CancelAsync submits a request to terminate the pending background
operation and sets the CancellationPending property to true.
When you call CancelAsync, your worker method has an opportunity to
stop its execution and exit. The worker code should periodically check
the CancellationPending property to see if it has been set to true.
I have no idea about it. By the way the images do not work. Embed it in the question.
The code assigns a method that is executed when the BackgroundWorker starts and you hook up methods to report the progress and do cleanup / updates once the background work is complete.
BackgroundWorker.CancelAsync is often misunderstood. It does not stop any pending work but is merely a signal to the UI thread that the work has been canceled! It just sets the CancellationPending property, which you can poll in the DoWork regularly.
Unfortunately the MSDN example with the Thread.Sleep calls in the DoWork is a very silly one. Normally you call a blocking operation in DoWork, which is often completely UI-independent.
See my answer here for a more usable example.
1.
According to MSDN BackgroundWorker Class page, maybe you should add a break to the loop.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; i <= 10; i++)
{
if (worker.CancellationPending == true)
{
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
System.Threading.Thread.Sleep(500);
worker.ReportProgress(i * 10);
}
}
}
2.
Have no idea.
3.
The block 1 region is setting for BackgroundWorker event. In my case , it normally will appear at Form1.Designer.cs if I click the lightning icon in attribute to set the event.
So I'm making a C# app which has to continuously read and display the contents of a text file, while allowing the user to enter something into a text box and append it to the end of that very file.
I'm doing this by running my read method on a separate thread, however changing the variable which stores the display text-files contents is what's causing a problem. Initially I tried having a method which did this, however that's not working and gave a 'cross-thread-operation-not-valid' error. I then tried applying some code I found on MSDN, but now after updating the variable once the thread ended!
Please help.
partial class MainForm
{
delegate void SetTextCallback(string text);
public static string msg;
public static string name;
public void InitClient()
{
name = "public.txt";
Console.WriteLine(name);
if(!File.Exists(name))
{
File.Create(name);
File.AppendAllText(name, "Welcome to " + name);
}
Thread Read = new Thread(new ThreadStart(this.Client));
Read.Start();
while(!Read.IsAlive);
}
public void WriteText()
{
File.AppendAllText(name, this.InputBox.Text);
this.InputBox.Clear();
}
private void SetText(string text)
{
if (this.OutPut.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.OutPut.Text = text;
}
}
public void Client()
{
msg = File.ReadAllText(name);
Console.WriteLine(msg);
Thread.Sleep(300);
this.SetText(msg);
}
}
Why is the thread behaving like this. How can I modify my code so that the contents of the output box always equals that of the text file.
Any suggestions welcome.
You've got multiple problems here,
the use of the File is probably not thread-safe.
your method does not repeat
your are Sleep()ing on a Thread
You can solve all of them by ditching the Thread and use a simple Timer.
Try using a background worker instead of creating a new thread. The background worker will run its content in a seperate thread, and allows you to report 'progress' while its working. This progress report will always be run on the UI-thread (or the thread which started the background worker).
It also has an event which is called when the background worker is finished. This is also run on the UI thread.
This example should get you started.
Update: Added some very basic error handling as suggested
The idea is to use the UserData (2nd argument) of ReportProgress to do updates on the UI thread whenever you need to. In this case it is a string, but this can be any object.
Furthermore, you can use the Result of the DoWorkEventArgs to produce a final result from the background work. In this case, I return any exception which was thrown, or null otherwise, but you can return whatever you want here as well.
It is, as Henk mentioned in his comment, very important to handle errors that occur inside the DoWork callback, because exceptions etc which occurs here will be swallowed and the worker will complete as if nothing bad happened.
private BackgroundWorker _backgroundWorker;
public Form1()
{
InitializeComponent();
_backgroundWorker = new BackgroundWorker();
_backgroundWorker.WorkerReportsProgress = true;
_backgroundWorker.WorkerSupportsCancellation = true;
// This is the background thread
_backgroundWorker.DoWork += BackgroundWorkerOnDoWork;
// Called when you report progress
_backgroundWorker.ProgressChanged += BackgroundWorkerOnProgressChanged;
// Called when the worker is done
_backgroundWorker.RunWorkerCompleted += BackgroundWorkerOnRunWorkerCompleted;
}
private void BackgroundWorkerOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs runWorkerCompletedEventArgs)
{
if (runWorkerCompletedEventArgs.Result != null)
{
// Handle error or throw it
throw runWorkerCompletedEventArgs.Result as Exception;
}
textBox1.Text = "Worker completed";
}
private void BackgroundWorkerOnProgressChanged(object sender, ProgressChangedEventArgs progressChangedEventArgs)
{
textBox1.Text = progressChangedEventArgs.UserState as string;
}
private void BackgroundWorkerOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
{
try
{
for (int i = 0; i < 100 && !_backgroundWorker.CancellationPending; i++)
{
_backgroundWorker.ReportProgress(0, i + " cycles");
Thread.Sleep(100);
}
}
catch (Exception ex)
{
doWorkEventArgs.Result = ex;
}
}
private void startButton_Click(object sender, EventArgs e)
{
if (!_backgroundWorker.IsBusy)
_backgroundWorker.RunWorkerAsync();
}
private void cancelButton_Click(object sender, EventArgs e)
{
if(_backgroundWorker.IsBusy)
_backgroundWorker.CancelAsync();
}
Below is my coding:
Form2 msgForm;
private void button3_Click_1(object sender, EventArgs e)
{
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
//bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
msgForm = new Form2();
try
{
bw.RunWorkerAsync();
msgForm.ShowDialog();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
if (comboBox15.Text == "")
{
//MessageBox.Show("Please select Printer ID.", "Status", MessageBoxButtons.OK, MessageBoxIcon.Error);
//return;
}
// Coding that transmit protocol and will last around 2 minutes.
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
msgForm.Close();
}
When I run this background worker coding, there's an error stating "Cross-thread operation not valid: Control 'comboBox15' accessed from a thread other than the thread it was created on."
How do I solve this problem guys?
You can use Invoke:
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.comboBox15.InvokeRequired)
{
this.Invoke((MethodInvoker) delegate {if (comboBox15.Text == ""){// What you want to do}});
}
else
{
if (comboBox15.Text == "")
{
}
}
also read the following:
http://msdn.microsoft.com/en-us/library/ms171728(v=vs.80).aspx
http://msdn.microsoft.com/en-us/library/aa288468(v=vs.71).aspx
Anonymous method in Invoke call
You can't UI elements from a non-UI-thread. Ideally, provide the relevant information to the background worker before it starts, e.g.
string text = combo15.Text;
bw.DoWork += (sender, args) => TransmitStuff(combo15.Text, args);
...
void TransmitStuff(string printerId, DoWorkEventArgs e)
{
...
}
If you can use .NET 4.5 and C# 5, you could use an async method to quite possibly make all of this easier... but I realize that's unlikely to be an option for you.
EDIT: While you can use Invoke, that ends up being quite messy - and you've got potentially inconsistent state. I generally think it's tidier to work out all the state you need before you start the long-running operation, validate it all, and then hand it to the operation. If you need to update the UI during the operation, you can use the BackgroundWorker progress facilities.
In BackgroundWorker, when we call any user controls its problem. Please use this property in Window Form Load event:
CheckForIllegalCrossThreadCalls = false;
You can only access gui controls from your main thread.
Move the
if (comboBox15.Text == "")
part to button3_click
You can get round it by passing the value such as below.
private void Dowork()
{
backgroundWorker1.RunWorkerAsync(comboBox1.Text);
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
String selected = (String)e.Argument;
if (String.IsNullOrEmpty(selected)) return;
//do stuff
}
I have a timer and a background worker, which initiates on every timer tick. Sometimes I need for user to stop this workflow and call that same bg worker from a button click.
Since it is in Silverlight it is all async. BG worker make async Webservice call, which asynchronously returns data.
I have done this, but it just feels wrong. What are best ways to handle that kind of situation?
button_click_event(..)
{
_loadTimer.Stop();
_worker.CancelAsync();
_worker.RunWorkerAsync();
}
WebService call
public void GetUserStats(DateTime start, DateTime end, Action<IEnumerable<IUserStats>, Exception> callback)
{
_context.GetUserStatsCompleted += ContextGetUserStatsCompleted;
_context.GetUserStatsAsync(start,end,callback);
}
void ContextGetUserStatsCompleted(object sender, GetUserStatsCompletedEventArgs e)
{
var callback = e.UserState as Action<IEnumerable<IUserStats>, Exception>;
Exception error = null;
var result = new ObservableCollection<IUserStats>();
if (e.Error == null)
{
result = e.Result;
}
else
{
error = e.Error;
}
_context.GetUserStatsCompleted -= ContextGetUserStatsCompleted;
callback(result, error);
}
and my worker
void WorkerDoWork(object sender, DoWorkEventArgs e)
{
TicketService.GetUserStats(StartDate, EndDate, (result, error) =>
{
StreamHolder = result;
});
}
Firs of all, you should always check to see if your worker is running, prior to attempting to run it again. If you don't then it's possible that your application will throw an exception.
if(!_worker.IsBusy)
{
_worker.RunWorkerAsync();
}
Second of all, just calling CancelAsync() is not enough to cancel the current operation of the background worker. You will have to add code to the background worker's DoWork event handler. (In your case WorkerDoWork)
if(_worker.CancelationPending == true)
{
e.Cancel = true;
return;
}
You can read more about the proper way to use a background worker here:
http://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx
I've got a threaded invoke call that never returns.
The thread runs just fine right up until I call the line that ways, "owner.Invoke(methInvoker);"
When debugging, I can slowly step, step, step, but once I hit owner.Invoke... it's Over!
Control owner;
public event ReportCeProgressDelegate ProgressChanged;
public void ReportProgress(int step, object data) {
if ((owner != null) && (ProgressChanged != null)) {
if (!CancellationPending) {
ThreadEventArg e = new ThreadEventArg(step, data);
if (owner.InvokeRequired) {
MethodInvoker methInvoker = delegate { ProgressChanged(this, e); };
owner.Invoke(methInvoker);
} else {
ProgressChanged(this, e);
}
} else {
mreReporter.Set();
mreReporter.Close();
}
}
}
FYI: This is a custom class that mimics the BackgroundWorker class, which is not available on controls that do not have Forms.
Thinking Invoke might not be required, I manually stepped the cursor in the debugger over that part of the code and tried calling ProgressChanged directly, but VS2010's debugger threw a cross thread exception.
EDIT:
Due to the first 3 comments I have received, I wanted to update with my ProgressChanged method:
worker.ProgressChanged += delegate(object sender, ThreadEventArg e) {
if (progressBar1.Style != ProgressBarStyle.Continuous) {
progressBar1.Value = 0;
object data = e.Data;
if (data != null) {
progressBar1.Maximum = 100;
}
progressBar1.Style = ProgressBarStyle.Continuous;
}
progressBar1.Value = e.ProgressPercentage;
};
There is a breakpoint on the first line of the anonymous method, but it never gets hit either.
EDIT 2
Here is a more complete listing of the call to the thread:
List<TableData> tList = CollectTablesFromForm();
if (0 < tList.Count) {
using (SqlCeReporter worker = new SqlCeReporter(this)) {
for (int i = 0; i < tList.Count; i++) {
ManualResetEvent mre = new ManualResetEvent(false);
worker.StartThread += SqlCeClass.SaveSqlCeDataTable;
worker.ProgressChanged += delegate(object sender, ThreadEventArg e) {
if (progressBar1.Style != ProgressBarStyle.Continuous) {
progressBar1.Value = 0;
object data = e.Data;
if (data != null) {
progressBar1.Maximum = 100;
}
progressBar1.Style = ProgressBarStyle.Continuous;
}
progressBar1.Value = e.ProgressPercentage;
};
worker.ThreadCompleted += delegate(object sender, ThreadResultArg e) {
Cursor = Cursors.Default;
progressBar1.Visible = false;
progressBar1.Style = ProgressBarStyle.Blocks;
if (e.Error == null) {
if (e.Cancelled) {
MessageBox.Show(this, "Save Action was Cancelled.", "Save Table " + tList[i].TableName);
}
} else {
MessageBox.Show(this, e.Error.Message, "Error Saving Table " + tList[i].TableName, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
mre.Set();
};
worker.RunWorkerAsync(tList[i]);
progressBar1.Value = 0;
progressBar1.Style = ProgressBarStyle.Marquee;
progressBar1.Visible = true;
Cursor = Cursors.WaitCursor;
mre.WaitOne();
}
}
}
I hope this isn't overkill! I hate presenting too much information, because then I get people critiquing my style. :)
worker.RunWorkerAsync(tList[i]);
//...
mre.WaitOne();
That's a guaranteed deadlock. The delegate you pass to Control.Begin/Invoke() can only run when the UI thread is idle, having re-entered the message loop. Your UI thread isn't idle, it is blocked on the WaitOne() call. That call can't complete until your worker thread completes. Your worker thread can't complete until the Invoke() call is completed. That call can't complete until the UI thread goes idle. Deadlock city.
Blocking the UI thread is fundamentally a wrong thing to do. Not just because of .NET plumbing, COM already requires it to never block. That's why BGW has a RunWorkerCompleted event.
It is likely that you have deadlocked the UI and worker threads. Control.Invoke marshals the execution of a delegate onto the UI thread by posting a message to the UI thread's message queue and then waits for that message to be processed which in turn means the execution of the delegate has to complete before Control.Invoke returns. But, what if your UI thread is busy doing something else beside dispatching and processing messages? I can see from your code that a ManualResetEvent may be in play here. Is your UI thread blocked on a call to WaitOne by chance? If so that could definitely be the problem. Since WaitOne does not pump messages it will block the UI thread which will lead to a deadlock when Control.Invoke is called from your worker thread.
If you want your ProgressChanged event to behave like it does with BackgroundWorker then you will need to call Control.Invoke to get those event handlers onto the UI thread. That is the way BackgroundWorker works anyway. Of course, you do not have to mimic the BackgroundWorker class exactly in that respect as long as you are prepared to have the callers do their own marshaling when handling the ProgressChanged event.