I have a task that takes a long time. I do it with a background worker thread and before start it, since Do_Work I begin an animation over a label and when task finishes, I stop it in RunWorkerCompleted but I received an error because I try to begin/stop animation in the background thread that is not the owner. How can I do this, I mean to begin/stop animation in the background worker?
thanks!
You should start the animation before starting the BackgroundWorker, not in the DoWork event. That way, you will be able to stop it from the RunWorkerCompleted event.
You need to use the Dispatcher.BeginInvoke method, on the control which is doing the animation.
You can also call the stop animation on the UI thread using something like the following:
private void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
/* If not operating on the main UI thread, call this method again on the App dispatcher's thread */
if (App.Current != null && App.Current.Dispatcher.Thread != Thread.CurrentThread)
{
App.Current.Dispatcher.Invoke(new RunWorkerCompletedEventHandler(OnRunWorkerCompleted), new object[] { sender, e});
return;
}
// Do stuff to the UI here
}
Related
I have a WPF utility tool that has a lot of processing to do. The processing is done from MainWindow.xaml which utilizes a BackgroundWorker to execute. What I want to do is open a new separate window called Logger.xaml and update a TextBox found on the Logger.xaml window on the progress of MainWindow's execution. The purpose of this is so that a user can continue to select more processing to do from MainWindow.xaml while still getting progress updates of ongoing work. So in layman's terms: 1 MainWindow to multiple Logger windows. Here is what I have so far.
MainWindow.xaml.cs:
private void btn_Click(object sender, RoutedEventArgs e)
{
bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += (a, b) => WorkerMethod(x, y); //WorkerMethod does all the heavy lifting
bw.RunWorkerAsync();
}
private void WorkerMethod(string x, string y)
{
/***some work 1 code***/
//Somehow open `Logger.xaml` and update its textbox that some work 1 finished.
/***some work 2 code***/
//Update its textbox that Work 2 finished.
etc...
}
Should I use BackgroundWorker.ProgressChanged somehow to open up a new Logger window on another thread and update the TextBox in that window? What is the best way to achieve the desired effect?
Should I use BackgroundWorker.ProgressChanged somehow to open up a new
Logger window on another thread and update the TextBox in that window?
Yes, BackgroundWorker raises ProgressChanged events on the current SynchronizationContext of the thread that called RunWorkerAsync()..
To receive notifications of progress updates, handle the ProgressChanged event. To receive a notification when the operation is completed, handle the RunWorkerCompleted event.
What is the best way to achieve the desired effect?
If you want to use a BackgroundWorker, probably this is the best.
I have a storyboard which I reuse to animate some pictures, I wanna perform some operation after each animation, which includes some calculations, and then running another animation, so I believe I should be using the StoryBoard's Completed Event MyStoryboard.Completed += storyboard_Com​pleted;
What I'm curious about is, should I start the next animation in the current StoryBoard's Storyboard_Completed Event? And, are there any implications if I started the first animation in a separate thread using the Application.Current.Dispatcher Object?
If I called a StoryBoard.Begin() in a separate thread using the Application.Current.Dispatcher, does the Storyboard_Completed Event also get invoked in the UI thread? In this case, do I still need to wrap the Next Animation within another Dispatcher Invoke?
private void Story_Completed(object sender, EventArgs e)
{
Application.Current.Dispatcher.Invoke((Action)delegate()
{
SomeNewStoryBoardAnimation.Begin();
}
}
Is this correct? Or is there a better way to check if a storyboard has ended and start the next set of calculations & storyboard animation right after that?
I've thought of using a single background worker to handler all animations and calculations in sequence, but I'm also wondering how to "wait" for the animation to complete before starting on the next set of calculations and animations. Is it normal for a BackGroundWorker to have Thread.sleep while waiting for animation to complete?
You could wrap the Storyboard in a Task object and await its completion.
Here is an excellent bit of sample code illustrating how to do just that, taken from a blog post by Morten Nielsen:
public static class StoryboardExtensions
{
public static Task BeginAsync(this Storyboard storyboard)
{
System.Threading.Tasks.TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
if (storyboard == null)
tcs.SetException(new ArgumentNullException());
else
{
EventHandler<object> onComplete = null;
onComplete = (s, e) => {
storyboard.Completed -= onComplete;
tcs.SetResult(true);
};
storyboard.Completed += onComplete;
storyboard.Begin();
}
return tcs.Task;
}
}
Essentially you're creating an extension method, which returns a Task object signalling the completion of the Storyboard. In this way, you get some nice fluid syntax like this:
//Start the storyboard and asynchronously await completion...
await myStoryboard.BeginAsync();
//Do my other stuff here, after the storyboard completes...
Using the Storyboard.Completed event should work for your purposes. The Storyboard.Completed event handler should fire on the UI thread, so you should not need to call Application.Current.Dispatcher.Invoke to fire off the second Storyboard.
There should be no implications if you call the original Storyboard.Begin using Application.Current.Dispatcher.Invoke. This won't launch the storyboard animation on a new thread. It will asynchronously invoke the animation on the main UI thread. Whether you call Begin on the UI thread yourself or whether you use Application.Current.Dispatcher.Invoke to do it, the final result should be the same. Your completed event handler will fire when the storyboard finishes, and you can perform your calculations and fire off the next storyboard.
See the following question for some discussion of storyboard having being used in the past as a timer because of the fact that it runs on the UI thread:
What is the point of using Storyboard as timer?
Also, this is probably overkill for the specific case you are describing, but if you need to orchestrate a bunch of sequential, asynchronous operations, you could use Reactive Extensions:
http://msdn.microsoft.com/en-us/data/gg577609.aspx
The following article includes a sequential storyboard example (though the article is old enough that the syntax has probably changed):
http://www.wintellect.com/cs/blogs/jlikness/archive/2010/08/22/coroutines-for-asynchronous-sequential-workflows-using-reactive-extensions-rx.aspx
I've seen some examples of code where StoryBoard is used as Timer, such as:
void Update()
{
if (_sb == null)
{
_sb = new Storyboard();
_sb.Completed += _sb_Completed;
_sb.Duration = new Duration(TimeSpan.FromSeconds(1));
}
if (_sb_completed)
{
_sb.Begin();
_sb_completed = false;
}
}
void _sb_Completed(object sender, EventArgs e)
{
PerformUpdate();
_sb_completed = true;
}
Is Storyboard at some point better than Timer? Why do people use it?
P.S. Question is related to Silverlight and/or WPF.
A Storyboard, like a DispatcherTimer, runs on the UI thread. So in either case you will never see a cross thread exception.
You can use a Storyboard over a DispatcherTimer because the Storyboard has a higher priority.
But im not sure about the Timer object itself as I've never really used it in favor of the Storyboard or DispatcherTimer.
Using a Storyboard is different from using a standard System.Threading.Timer. The Storybroad operates on the main thread hence the Completed even can manipulate the UI elements without getting cross thread exceptions. The standard Timer callback doesn't run on the UI thread and will therefore need additional help to manipulate UI elements.
However as Mark points out if all that is really needed is a delay then a DispatcherTimer would be the more intuative choice. The difference with DispatcherTimer is that its designed to invoke its Tick event regularly whereas the Storyboard will only call Completed at most once for each call to Begin. A DispatcherTimer can be used in this way by calling its Stop method in the first Tick event occurance.
I have a task that takes a long time to execute. In order to inform the user of the progress, I have a progress bar that I update inside DoWork.
Can anybody tell me if this is the best way to update the progress bar? I have heard that there is a ReportProgress event handler but I am confused because I'm unsure of the purpose of ReportProgress.
Since the Background worker works in a separate thread, you'll run into problems if you try to access UI objects. Calling the ReportProgress method on the worker from inside the DoWork handler raises the ProgressChanged event. That event should be handled in the UI thread so as to easily access the control.
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += DoWorkHandler;
worker.WorkerReportsProgress = true;
worker.ProgressChanged += (s, e) =>
{ myProgressBar.Value = e.ProgressPercentage; };
worker.RunWorkerAsync();
...
public void DoWorkHandler(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
while (working)
{
// Do Stuff
worker.ReportProgress(progressPercentage);
}
}
The ProgressChanged event is what you are looking for. However, make sure you create the BackgroundWorker like below so it actually raises this event when ReportProgress is called.
BackgroundWorker bw = new BackgroundWorker() { WorkerReportsProgress = true };
bw.ProgressChanged += ... ;
ReportProgress is what you would use to update the progress of your task, including things like the UI--in your case, a proggress bar.
You should check out the MSDN docs, located here.
basically, you create a handler for the ReportProgress event, then in your DoWorkEventHandler, you call the ReportProgress like so:
worker.ReportProgress((i * 10));
The LoadIt() method below takes 5-10 seconds to complete.
I want the message area to display "Loading..." before LoadIt() starts and display "Reloaded" after it finishes.
How can I do that?
The following code doesn't work. It seems to not update the label until everything is finished, at which point it just displays "Reloaded" again.
private void Button_Click(object sender, RoutedEventArgs e)
{
lblMessage.Text = "Loading...";
LoadIt();
lblMessage.Text = "Reloaded";
}
There's more than one solution discussed here:
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/6fce9b7b-4a13-4c8d-8c3e-562667851baa/
you could move LoadIt to a separate thread, or you could simulate the WinForms Application.DoEvents but this is quite a hack (http://shevaspace.spaces.live.com/blog/cns!FD9A0F1F8DD06954!526.entry)
You can use the Dispatcher object to start tasks in the background thread:
public delegate void LoadItDelegate();
this.Dispatcher.BeginInvoke(
System.Windows.Threading.DispatcherPriority.Background,
new LoadItDelegate(LoadIt));
Make sure its in the background, because the UI thread has more priority, so the UI gets updated.. and also move your "I am done message" to the end of your LoadIt method :)