I have a button that performs an action that takes 3 seconds (reads data from an API and refreshes the screen). While the action is performed, I would like the button to rotate.
My problem is that it starts to rotate AFTER the 3seconds operation has been completed...
and, ofc it stops immediately, as it hits ClearAnimation..
Here's my code
this.refreshButton.Click += this.RefreshPendingOrders_Click;
private void RefreshPendingOrders_Click(object sender, EventArgs e)
{
this.StartRotateAnimation();
System.Threading.Thread.Sleep(3000);
this.refreshButton.ClearAnimation();
}
private void StartRotateAnimation()
{
var pivotX = this.refreshButton.Width / 2;
var pivotY = this.refreshButton.Height / 2;
var animation = new RotateAnimation(0f, 360f, pivotX, pivotY)
{
Duration = 500,
RepeatCount = 3
};
this.refreshButton.StartAnimation(animation);
}
The issue is that you are calling Thread.Sleep() which in case of a Click EventHandler will be happening on the UI thread. This means you are blocking the UI Thread, which is responsible for running your animations for 3 seconds.
You could change your code to use async/await instead like:
private async void RefreshPendingOrders_Click(object sender, EventArgs e)
{
this.StartRotateAnimation();
await Task.Delay(3000);
this.refreshButton.ClearAnimation();
}
Related
I have a method tied to a button that does time consuming thing. So I have created a label and binded it to property to inform user that application is actually doing something.
private void Button_OnClick(object sender, RoutedEventArgs e)
{
_viewModel.BottomBarMessage = "Loading..."; //part 1
//do very long thing, for example
System.Threading.Thread.Sleep(3000); //wait for 3 seconds to simulate long operation
_viewModel.BottomBarMessage = "Done."; //part 2
}
Issue is part 1 does not seem to be ever executed. Label switches to Done but never to Loading.
I have suspected its because UI gets blocked, but changing Sleep to be executed as Task changes nothing.
use await Task.Delay instead of Thread.Sleep - you are blocking UI thread.
Something like:
private async void Button_OnClick(object sender, RoutedEventArgs e)
{
_viewModel.BottomBarMessage = "Loading..."; //part 1
//do very long thing, for example
await Task.Delay(3000);
_viewModel.BottomBarMessage = "Done."; //part 2
}
I am wondering what would be the best solution to a practical problem.
I am not using any threads in this small project.
It is a simple UI polling data from the serial port on a fixed timer.
Data is analyzed, filtered, and then displayed on a line chart.
Everything is working fine other than data polling "hanging" (i.e. not being executed, no error or anything) when the form is moved around on the desktop.
I don't necessarily need the chart to be updated when the form is being moved but I would at least want the timer to still tick while it's being moved (so that data polling continues).
My timer is declared as follow:
private System.Windows.Forms.Timer timer1;
I don't have any threads defined myself, but my understanding is that timer ticks are taking place on a separate thread. Is that right?
I am handling the timer tick event like this.
private void timer1_Tick(object sender, EventArgs e)
{
if (chkBoxPosition.Checked)
{
tBoxPosition.Text = ExecuteCommand("r 1\n", tBoxPosition.Text, false, false);
Axis.position = TryParseDouble(Axis.position, tBoxPosition.Text);
}
}
I have more stuff in this event (a chart).
It's working decently, however when I drag the UI winform on the desktop it "freezes" the controls temporarily until I let go of the UI. It doesn't crash or anything, it just doesn't refresh as I am moving the window.
Not a huge deal as far as the controls are concerned, however, I just realized that the entire thread or timer were also hanging, as for the entire time where I am holding the mouse button dragging the window around nothing seems to be taking place.
The question does not specify what chart package is being used, so i'm guessing the built in one from .Net.
The background worker is a good option, but it's pretty old school and takes more implementation than using async Task.
Note: The only time it's OK to use async void is on top level event handlers, otherwise you should use async Task, see this
I made it so the checkbox enables and disabled the timer, not sure what it does in your app.
Here are 2 versions of your code, v1 is where the text box gets updated after the ExecuteCommand has finished and v2 is where the text box gets updated within the ExecuteCommnad method.
Version 1: (update text on timer tick)
public partial class Form1 : Form
{
Series Series1 { get; set; }
int val = 0;
public Form1()
{
InitializeComponent();
timer1.Interval = 2000;
chart1.Series.Clear();
Series1 = new Series
{
Name = "Series1",
Color = System.Drawing.Color.Green,
IsVisibleInLegend = false,
IsXValueIndexed = true,
ChartType = SeriesChartType.Line
};
this.chart1.Series.Add(Series1);
}
private async void timer1_Tick(object sender, EventArgs e)
{
tBoxPosition.Text = await ExecuteCommand("r 1\n", tBoxPosition.Text, false, false);
tBoxPosition.Select(tBoxPosition.Text.Length - 1, 0);
tBoxPosition.ScrollToCaret();
}
//not sure what you are doing her but lets say its something that takes some time..maybe a data fetch of some kind
async Task<string> ExecuteCommand(string str, string text, bool value1, bool value2)
{
StringBuilder returnString = new StringBuilder(text);
for (int i = 0; i < 10; i++)
{
await Task.Delay(100);
returnString.AppendLine($"value : {val++}");
Series1.Points.AddXY(i, (i + i + i));
}
return returnString.ToString();
}
private void chkBoxPosition_CheckedChanged(object sender, EventArgs e)
{
if (chkBoxPosition.Checked)
timer1.Enabled = true;
else
timer1.Enabled = false;
}
}
version 2: (update text box and chart in execute method)
private async void timer1_Tick(object sender, EventArgs e)
{
await ExecuteCommand("r 1\n", tBoxPosition.Text, false, false);
}
//not sure what you are doing her but lets say its something that takes some time..maybe a data fetch of some kind
async Task ExecuteCommand(string str, string text, bool value1, bool value2)
{
for (int i = 0; i < 10; i++)
{
await Task.Delay(100);
Series1.Points.AddXY(i, (i + i + i));
tBoxPosition.Text += $"value : {val++} {Environment.NewLine}";
tBoxPosition.Select(tBoxPosition.Text.Length - 1, 0);
tBoxPosition.ScrollToCaret();
}
}
I am making an app for wp 7.x/8.
There is a function like -
public void draw()
{
.............
ImageBrush imgbrush = new ImageBrush();
imgbrush.ImageSource = new BitmapImage(...);
rect.Fill = imgbrush; //rect is of type Rectangle that has been created
//and added to the canvas
........
}
and another function
private void clicked(object sender, RoutedEventArgs e)
{
draw();
........
if (flag)
{
Thread.sleep(5000);
draw();
}
}
But when the button is clicked the result of both the draw operation appear simultaneously on the screen.
How to make the result of second draw() operation to appear after some delay?
Or is there something like buffer for the screen, until the buffer is not filled the screen will not refresh?
In that case, how to FLUSH or Refresh the screen explicitly or force the .fill() method of Rectangle to make the changes on the screen?
Any help in this regard would be highly appreciated.
As pantaloons says, since all of your actions are on the same thread (the first draw, the sleep, and the second draw), the UI itself never gets a chance to update. However, there is a slightly better implementation (though it follows the same principal as the aforementioned suggestion).
By using a timer, you can let it kick the wait to another thread, allowing the UI to update from the first draw before doing the second, like so:
private void clicked(object sender, RoutedEventArgs e)
{
draw();
........
if (flag)
{
var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(5) };
timer.Tick += (sender, args) => { timer.Stop(); draw(); };
timer.Start();
}
}
In this solution, all the invocation is handled by the DispatcherTimer (which will automatically call back to the UI thread). Also, if draw needs to be called more than twice in a row, the timer will continue to tick until stopped, so it would be very straightforward to extend to include a count.
The problem is that you are blocking the UI thread by sleeping, so the draw messages are never pumped until that function returns, where the changes happen synchronously. A hacky solution would be something like the following, although really you should change your app design to use the async/await patterns.
private void clicked(object sender, RoutedEventArgs e)
{
draw();
if (flag)
{
System.Threading.Tasks.Task.Run(() =>
{
System.Threading.Thread.Sleep(5000);
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
draw();
});
});
}
}
EDIT #1: I have placed worker.RunWorkerAsync() within my timer loop and my application does not shut down anymore. Although nothing seems to happen now.
For performance reasons i need to replace DispatcherTimers with a other timer that runs in a different thread. There are to much delays / freezes so DispatcherTimer is no longer a option.
I am having problems to actually update my GUI thread, my application always seems to shut down without any warnings / errors.
I have mainly been trying to experiment with BackGroundWorker in attempt to solve my problem. Everything results in a shut down of my application when i launch it.
Some code examples would be greatly apperciated.
Old code dispatcher code:
public void InitializeDispatcherTimerWeging()
{
timerWegingen = new DispatcherTimer();
timerWegingen.Tick += new EventHandler(timerWegingen_Tick);
timerWegingen.Interval = new TimeSpan(0, 0, Convert.ToInt16(minKorteStilstand));
timerWegingen.Start();
}
private void timerWegingen_Tick(object sender, EventArgs e)
{
DisplayWegingInfo();
CaculateTimeBetweenWegingen();
}
Every 5 seconds the DisplayWegingInfo() and Calculate method should be called upon.
The GUI updates happen in the Calculate method. There a button gets created dynamically and added to a observerableCollection.
Button creation (short version):
public void CreateRegistrationButton()
{
InitializeDispatcherTimerStilstand();
RegistrationButton btn = new RegistrationButton(GlobalObservableCol.regBtns.Count.ToString());
btn.RegistrationCount = GlobalObservableCol.regBtnCount;
btn.Title = "btnRegistration" + GlobalObservableCol.regBtnCount;
btn.BeginStilstand = btn.Time;
GlobalObservableCol.regBtns.Add(btn);
GlobalObservableCol.regBtnCount++;
btn.DuurStilstand = String.Format("{0:D2}:{1:D2}:{2:D2}", 0, 0, 0);
}
New code using threading timer that runs in a different thread then the GUI
public void InitializeDispatcherTimerWeging()
{
worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(Worker_DoWork);
worker.RunWorkerAsync();
}
void Worker_DoWork(object sender, DoWorkEventArgs e)
{
TimerCallback callback = MyTimerCallBack;
timerWegingen = new Timer(callback);
timerWegingen.Change(0, 5000);
}
private void MyTimerCallBack(object state)
{
DisplayWegingInfo();
CaculateTimeBetweenWegingen();
}
I timer runs in a separate thread then the GUI thread (that dispatcherTimer uses). But i cannot seem to be able to send this update to the UI thread itself so the updates get actually implemented in the UI.
The button gets refilled with new values every 1 sec trough a other timer. "DuurStilstand" is a dependency property
private void FillDuurStilstandRegistrationBtn()
{
TimeSpan tsSec = TimeSpan.FromSeconds(stopWatch.Elapsed.Seconds);
TimeSpan tsMin = TimeSpan.FromMinutes(stopWatch.Elapsed.Minutes);
TimeSpan tsHour = TimeSpan.FromMinutes(stopWatch.Elapsed.Hours);
if (GlobalObservableCol.regBtns.Count >= 1
&& GlobalObservableCol.regBtns[GlobalObservableCol.regBtns.Count - 1].StopWatchActive == true)
{
GlobalObservableCol.regBtns[GlobalObservableCol.regBtns.Count - 1].DuurStilstand =
String.Format("{0:D2}:{1:D2}:{2:D2}", tsHour.Hours, tsMin.Minutes, tsSec.Seconds);
}
}
Would i need to use the invoke from Dispatcher in the above method? If so how exactly?
Not sure how to call the ui thread after initializing the doWork method of the BackGroundWorker, my application keeps shutting down after right after start up.
I have tried using Dispatcher.BeginInvoke in several methods but all failed so far. At the moment i have no clue how to implement it.
All the above code is written in a separate c# class.
Best Regards,
Jackz
When I ran my sample of your code, the DisplayWegingInfo() was throwing an exception trying to access UI components. We need to call Invoke() from the Timer thread to update the UI. See DisplayWegingInfo() below. Note: this assumes that CaculateTimeBetweenWegingen() does not interact with the UI.
void Worker_DoWork(object sender, DoWorkEventArgs e)
{
TimerCallback callback = MyTimerCallBack;
timerWegingen = new System.Threading.Timer(callback);
timerWegingen.Change(0, 3000);
}
private void MyTimerCallBack(object state)
{
DisplayWegingInfo();
CaculateTimeBetweenWegingen();
}
private void DisplayWegingInfo()
{
if (this.InvokeRequired)
{
this.Invoke(new Action(DisplayWegingInfo));
return;
}
// at this point, we are on the UI thread, and can update the GUI elements
this.label1.Text = DateTime.Now.ToString();
}
private void CaculateTimeBetweenWegingen()
{
Thread.Sleep(1000);
}
Is there a way to directly "restart" a background worker?
Calling CancelAsync() followed by RunWorkerAsync() clearly won't do it as their names imply.
Background info:
I have a background worker which calculates a total in my .net 2.0 Windows Forms app.
Whenever the user modifies any value which is part of this total I'd like to restart the background worker in case it would be running so that directly the latest values are considered.
The backgriound work itself does not do any cancleing.
When you call bgw.CancelAsync it sets a flag on the background worker that you need to check yourself in the DoWork handler.
something like:
bool _restart = false;
private void button1_Click(object sender, EventArgs e)
{
bgw.CancelAsync();
_restart = true;
}
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 300; i++)
{
if (bgw.CancellationPending)
{
break;
}
//time consuming calculation
}
}
private void bgw_WorkComplete(object sender, eventargs e) //no ide to hand not sure on name/args
{
if (_restart)
{
bgw.RunWorkerAsync();
_restart = false;
}
}
There are a couple of options, it all depends on how you want to skin this cat:
If you want to continue to use BackgroundWorker, then you need to respect the model that has been established, that is, one of "progress sensitivity". The stuff inside DoWork is clearly required to always be aware of whether or not the a pending cancellation is due (i.e., there needs to be a certain amount of polling taking place in your DoWork loop).
If your calculation code is monolithic and you don't want to mess with it, then don't use BackgroundWorker, but rather fire up your own thread--this way you can forcefully kill it if needs be.
You can hook the change event handler for the controls in which the values are changed and do the following in the handler:
if(!bgWrkr.IsBusy)
//start worker
else if(!bgWrkr.CancellationPending)
bgWrkr.CancelAsync();
Hope it helps you!
I want to leave my requests running, but no longer care about the results. I override the value of the background worker (my busy spinner is using the isBusy flag).
private void SearchWorkerCreate() {
this.searchWorker = new BackgroundWorker();
this.searchWorker.DoWork += this.SearchWorkerWork;
this.searchWorker.RunWorkerCompleted += this.SearchWorkerFinish;
}
private void SearchWorkerStart(string criteria){
if(this.searchWorker.IsBusy){
this.SearchWorkerCreate();
}
this.searchWorker.RunWorkerAsync(criteria);
this.OnPropertyChanged(() => this.IsBusy);
this.OnPropertyChanged(() => this.IsIdle);
}
May this method help someone... I've created a function to reset the backgroundworker in one method. I use it for task to do periodically.
By creating a Task, the backgroundworker is can be stopped with the CancelAsync and restarted inside the Task. Not making a Task wil start the backgroundworker again before it is cancelled, as the OP describes.
The only requirement is that your code runs through some loop, which checks the CancellationPending every period of time (CheckPerMilliseconds).
private void ResetBackgroundWorker()
{
backgroundWorker.CancelAsync();
Task taskStart = Task.Run(() =>
{
Thread.Sleep(CheckPerMilliseconds);
backgroundWorker.RunWorkerAsync();
});
}
Inside the backgroundworker I use a for-loop that checks the CancellationPending.
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
while(true)
{
if (backgroundWorker.CancellationPending)
{
return;
}
//Do something you want to do periodically.
for (int i = 0; i < minutesToDoTask * 60; i++)
{
if (backgroundWorker.CancellationPending)
{
return;
}
Thread.Sleep(CheckPerMilliseconds);
}
}
}