WPF DispatcherFrame magic - how and why this works? - c#

I was trying to animate some stuff in WPF and run some other operations when animation finishes.
Also, wanted to avoid animation finish callback mechanism, so, I came up with a solution illustrated in the code below:
// Start one second of animation
...
// Pause for one second
Wait(this.Dispatcher, 1000);
// Continue and do some other stuff
...
Now, the interesting part is Wait method which magically makes blocking pause in my code but the animation and UI stays normal, responsive:
public static void Wait(Dispatcher Dispatcher, int Milliseconds)
{
var Frame = new DispatcherFrame();
ThreadPool.QueueUserWorkItem(State =>
{
Thread.Sleep(Milliseconds);
Frame.Continue = false;
});
Dispatcher.PushFrame(Frame);
}
I have read a documentation and a few articles about DispatcherFrame but I am still unable to figure out what is really happening under the hood, and I need some clarification about how this construction with PushFrame really works.

From MSDN:
PushFrame
Enters an execute loop.
That loop (i.e. the DispatcherFrame) executes as long as its Continue property is true.
As soon as the Continue property goes to false, the loop/frame is exited and the Dispatcher returns to the loop/frame that it executed before you called PushFrame.

If you want take a pause, why not anything like this?
private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
var btn = sender as Button;
btn.Content = "Before pause";
var animation = new DoubleAnimation();
animation.From = btn.ActualWidth;
animation.To = 100;
animation.Duration = TimeSpan.FromSeconds(2);
btn.BeginAnimation(Button.WidthProperty, animation);
await Task.Delay(2000);
btn.Content = "After pause";
}

Related

How can I run an expensive process on SelectionChanged but not when scrolling quickly. (Delay event handler)?

I am having to run what can be a fairly slow task every time the SelectionChanged event of a DataGrid is fired.
The problem I have is that I need to keep the application responsive and if the user is scrolling very quickly using the arrow keys then I don't want to execute the task for every item. Only the item they stop on. (I hope this is making sense!)
I have setup a very basic example to demonstrate, that displays a list of words in a DataGrid and then when you scroll through them it adds them to a ListView.
This is what I have tried so far:
CancellationTokenSource cts;
private bool loading;
private async void dgData_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (loading)
{
cts.Cancel();
return;
}
cts = new CancellationTokenSource();
loading = true;
var x = dgData.SelectedItem.ToString();
await Task.Run(async () =>
{
Thread.Sleep(1000); //Wait a second to see if scrolling quickly...
await ExpensiveProcess(x);
});
loading = false;
}
private async Task ExpensiveProcess(string text)
{
if (cts.IsCancellationRequested)
{
loading = false;
return;
}
await Task.Factory.StartNew(() =>
{
//Expensive process will be done here...
});
Application.Current.Dispatcher.Invoke(() =>
{
lvwItems.Items.Add(text);
});
loading = false;
}
This appears to work in the fact that if arrow down quickly it misses items, but when I stop on one and want it to run it doesn't work?
Where am I going wrong? Is this even the best approach? Any advice is greatly appreciated and happy to provide further information. Thank you in advance.
UPDATE:
I found a a video on YouTube that suggested doing this which is working as I'd expect so for now I am going for this, but leaving the question open for now for any feedback.
Create a timer which will run the expensive process and set the interval to something low but not too slow so the key presses.
var myTimer = new DispatcherTimer();
myTimer.Interval = TimeSpan.FromSeconds(1);
myTimer.Tick += MyTimer_Tick
On the tick event of the timer run the long running process.
private void MyTimer_Tick(object sender, EventArgs e)
{
var x = dgData.SelectedItem.ToString();
Task.Run(async () =>
{
Thread.Sleep(1000); //Needs to be removed
await ExpensiveProcess(x);
});
}
Then in regular SelectionChanged event simply Stop and Start timer. Also don't forget to Stop the timer at the end of the long process.
You could start a timer in the SelectionChanged event handler and then check whether the item is still selected when the timer elapses.
If it is, you call the long-running method with a CancellationToken that you cancel if another selection occurs.
The following sample code should give you the idea:
private CancellationTokenSource _cts = null;
...
dataGrid.SelectionChanged += async(ss, ee) =>
{
//cancel any previous long running operation
if (_cts != null)
{
_cts.Cancel();
_cts.Dispose();
}
_cts = new CancellationTokenSource();
//store a local copy the unique id or something of the currently selected item
var id = (dataGrid.SelectedItem as TestItem).Id;
//wait a second and a half before doing anything...
await Task.Delay(1500);
//if no other item has been selected since {id} was selected, call the long running operation
if (_cts != null && id == (dataGrid.SelectedItem as TestItem).Id)
{
try
{
await LongRunningOperation(id, _cts.Token);
}
finally
{
_cts.Cancel();
_cts.Dispose();
_cts = null;
}
}
};
Instead of using the event, if you use a data-binding on the SelectedItem property you can achieve this easily using the Delay property. Delay will wait n milliseconds before processing a change.
<DataGrid ...
SelectedItem="{Binding SelectedItem, Delay=1000}">
...
</DataGrid>

WPF Task Manager: Handling Refreshing CPU Load Value?

I am currently somewhat new to c#/wpf (and coding in general). I decided to start another project, being a custom made "task manager" of sorts.
(While I use binding, this is NOT a MVVM project, so all answers welcome)
If you have ever opened task manager, you know that one of the main helpful tools it provides is a updating view of CPU/RAM/Whatever usage. Telling the user what percent of the resource they are using.
My problem is not getting the CPU percentage. I am unsure on how to refresh the text property for CPU load in the UI efficiently.
My first thought was that I should create a Background worker (which is probably correct) to separate the thread loads. However, I can't seem to wrap my mind on the solution to implement the background workers in a useful way.
The code is currently set up in this fashion:
When page is loaded, public BgWrk creates a new instance of it self.
Adds task to be called when ran.
BgWrk is ran.
New instance of method to be called is made.
Dispatcher is invoked on main thread to update UI.
Invoke consists of setting public string PerCpu (bound in other class, using INotifyPropertyChanged & all) on the return value of "grabber"'s CpuPerUsed.
BgWrk disposed.
Program loops (this is most likely the problem).
private void Grid_Loaded(object sender, RoutedEventArgs e)
{
BgWrk = new BackgroundWorker();
BgWrk.DoWork += new DoWorkEventHandler(BackgroundWorker1_DoWork);
BgWrk.RunWorkerAsync();
}
private void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
CpuInfoGrabber grabber = new CpuInfoGrabber();
Application.Current.Dispatcher.Invoke(new Action (() => Bnd.PerCpu = grabber.CpuPerUsed()));
BgWrk.Dispose();
}
}
Again the code works, but it is WAY to slow due to the load of retrieving all of that data. Any suggestions on how to make this work well are appreciated!
Thanks
Instead of looping you could use a timer to periodically poll for the CPU usage.
class Test
{
private System.Timers.Timer _timer;
public Test( )
{
_timer = new System.Timers.Timer
{
// Interval set to 1 millisecond.
Interval = 1,
AutoReset = true,
};
_timer.Elapsed += _timer_Elapsed;
_timer.Enabled = true;
_timer.Start( );
}
private void _timer_Elapsed( object sender, System.Timers.ElapsedEventArgs e )
{
// This handler is not executed on the gui thread so
// you'll have to marshal the call to the gui thread
// and then update your property.
var grabber = new CpuInfoGrabber();
var data = grabber.CpuPerUsed();
Application.Current.Dispatcher.Invoke( ( ) => Bnd.PerCpu = data );
}
}
I'd use Task.Run instead of a BackgroundWorker in your case:
private void Grid_Loaded(object sender, RoutedEventArgs e)
{
//Keep it running for 5 minutes
CancellationTokenSource cts = new CancellationTokenSource(new TimeSpan(hours: 0, minutes: 5, seconds: 0));
//Keep it running until user closes the app
//CancellationTokenSource cts = new CancellationTokenSource();
//Go to a different thread
Task.Run(() =>
{
//Some dummy variable
long millisecondsSlept = 0;
//Make sure cancellation not requested
while (!cts.Token.IsCancellationRequested)
{
//Some heavy operation here
Thread.Sleep(500);
millisecondsSlept += 500;
//Update UI with the results of the heavy operation
Application.Current.Dispatcher.Invoke(() => txtCpu.Text = millisecondsSlept.ToString());
}
}, cts.Token);
}

ProgressRing is not showing while the app runs

I am developing a 'Universal App' for Windows 8.1 and Windows Phone 8.1
I need to add a ProgressRing control to my page, because there is a task which takes some time to complete and I should notify the user that the app is loading data. So I followed the instruction on MDSN in the following link:
To create an indeterminate progress ring
and they stated what I exactly need:
4. Adding an indeterminate progress ring
When you can't estimate how much work remains to finish a task and the
task does block user interaction, use an indeterminate progress ring.
An indeterminate progress ring shows an animate sequence of dots
moving in a circle.
The problem is that the ProgressRing doesn't play the animation of "loading", when I insert prog.IsActive = true; before the code that starts the task
If I comment the Task lines of code, it plays.
XAML :
<ProgressRing x:Name="prog" Margin="435,349,388,181" FontFamily="{StaticResource FontFamily6}" FontSize="25" />
C#:
private void LevelTap(object sender, TappedRoutedEventArgs e)
{
prog.IsActive = true;
CheckNav(sender);
}
private void CheckNav(object sender)
{
TextBlock tb = sender as TextBlock;
GameSetter game = new GameSetter();
game.Level = (int)TextDict[tb];
if (!LevelsArr[game.Level]) return;
if (!goToLeader)
{
prog.IsActive = false;
CallNav(true, game);
}
else
{
prog.IsActive = false;
CallNav(false, game);
}
}
private void CallNav(bool b,GameSetter g)
{
if(b) Frame.Navigate(typeof(Game8), g);
else Frame.Navigate(typeof(MainPage), g);
}
Note: The Task that runs uses Dictionary, Windows.Storage.ApplicationData, and Navigation for another page in the end.
You can use this code block in every function too if Progressing inDeterminate mode always open:
#region Progress Ring Start
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
Waiter.Visibility = Visibility.Visible; // Progress ring name is Waiter.
});
#endregion
await Task.Delay(1000);
CheckNav(sender); // Your process
#region Progress Ring Finish
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
Waiter.Visibility = Visibility.Collapsed; // Progress ring name is Waiter.
});
#endregion
The flaw in your code (except the fact that it's probably nearly instant anyway, as notes #kennyzx), is that you need the LevelTap method to return for the UI to be updated. And when the method returns, you've already set the ProgressRing.IsActive property back to false, which means nothing is displayed at all.
One simple way to fix that is to use the async Task.Yield method to give the UI thread a chance to run:
private async void LevelTap(object sender, TappedRoutedEventArgs e)
{
prog.IsActive = true;
await Task.Yield();
CheckNav(sender);
}
But almost nothing will be shown since your code shouldn't take long to execute. If you really want to force the indicator to be displayed for 0.5 seconds, you can do something like:
private async void LevelTap(object sender, TappedRoutedEventArgs e)
{
prog.IsActive = true;
await Task.Delay(500);
CheckNav(sender);
}

Interacting between two threads

I am working on a winform application, and my goal is to make a label on my form visible to the user, and three seconds later make the label invisible. The issue here is timing out three seconds. I honestly do not know if this was the correct solution to my problem, but I was able to make this work by creating a new thread, and having the new thread Sleep for three seconds (System.Threading.Thread.Sleep(3000)).
I can't use System.Threading.Thread.Sleep(3000) because this freezes my GUI for 3 seconds!
private void someVoid()
{
lbl_authenticationProcess.Text = "Credentials have been verified authentic...";
Thread sleepThreadStart = new Thread(new ThreadStart(newThread_restProgram));
sleepThreadStart.Start();
// Once three seconds has passed / thread has finished: lbl_authenticationProcess.Visible = false;
}
private void newThread_restProgram()
{
System.Threading.Thread.Sleep(3000);
}
So, back to my original question. How can I determine (from my main thread) when the new thread has completed, meaning three seconds has passed?
I am open to new ideas as well as I'm sure there are many.
Right now, you are blocking the entire UI thread in order to hide a label after 3 seconds. If that's what you want, then just user Thread.Sleep(3000) from within the form. If not, though, then you're best off using a Timer:
System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
timer.Interval = 3000;
timer.Tick += (s, e) => { this.lbl_authenticationProcess.Visible = false; timer.Stop(); }
timer.Start();
After 3 seconds, the label will disappear. While you're waiting for that, though, a user can still interact with your application.
Note that you must use the Forms version of Timer, since its Tick event is raised on the UI thread, allowing direct access to the control. Other timers can work, but interaction with the control would have to be Invoke/BeginInvoked.
Did you try to use Timer
System.Windows.Forms.Timer t = new System.Windows.Forms.Timer();
t.Interval = 3000;
t.Start();
t.Tick += new EventHandler(t_Tick);
void t_Tick(object sender, EventArgs e)
{
label.Visible = false;
}
You really don't need to synchronize anything. You just need a new thread, with a reference to your label. Your code is actually pretty close:
private void someVoid()
{
lbl_authenticationProcess.Text = "Credentials have been verified authentic...";
lbl_authenticationProcess.Visible = true;
Thread sleepThreadStart = new Thread(new ThreadStart(newThread_restProgram));
sleepThreadStart.Start();
}
private void newThread_restProgram()
{
System.Threading.Thread.Sleep(3000);
if (lbl_authenticationProcess.InvokeRequired) {
lbl_authenticationProcess.Invoke(new SimpleCallBack(makeInvisible));
} else {
makeInvisible();
}
}
private void makeInvisible()
{
lbl_authenticationProcess.Visible = false;
}
So, when someVoid() is called, the message on the label is set, the label is made visible. Then a new thread is started with the newThread_restProgram() as the body. The new thread will sleep for 3 seconds (allowing other parts of the program to run), then the sleep ends and the label is made invisible. The new thread ends automatically because it's body method returns.
You can make a method like so:
public void SetLbl(string txt)
{
Invoke((Action)(lbl_authenticationProcess.Text = txt));
}
And you would be able to call it from the second thread, but it invokes on the main thread.
If you're using .NET 3.5 or older, it's kinda a pain:
private void YourMethod()
{
someLabel.BeginInvoke(() =>
{
someLabel.Text = "Something Else";
Thread thread = new Thread(() =>
{
Thread.Sleep(3000);
someLabel.BeginInvoke(() => { someLabel.Visible = false; });
});
thread.Start();
});
}
That should stop you from blocking the UI.
If you're using .NET 4+:
Task.Factory.StartNew(() =>
{
someLabel.BeginInvoke(() => { someLabel.Text = "Something" });
}).ContinueWith(() =>
{
Thread.Sleep(3000);
someLabel.BeginInvoke(() => { someLabel.Visible = false; });
});
If you are willing to download the Async CTP then you could use this really elegant solution which requires the new async and await keywords.1
private void async YourButton_Click(object sender, EventArgs args)
{
// Do authentication stuff here.
lbl_authenticationProcess.Text = "Credentials have been verified authentic...";
await Task.Delay(3000); // TaskEx.Delay in CTP
lbl_authenticationProcess.Visible = false;
}
1Note that the Async CTP uses TaskEx instead of Task.
You can use an AutoResetEvent for your thread synchronization. You set the event to signalled when your secondary thread has woken from it's sleep, so that it can notify your main thread.
That means though that your main thread waits for the other thread to complete.
On that note, you can use SecondThread.Join() to wait for it to complete in your main thread.
You do either of the above, but you don't need to do both.
As suggested in the comments, having a UI thread sleep is not generally a good idea, as it causes unresponsiveness for the user.
However if you do that, you might as well just sleep your main thread and get rid of the extraneous need of the second thread.
I'm not exactly sure this is the right way to do it, but to answer your question, you have to use the Join() function.
public void CallingThread()
{
Thread t = new Thread(myWorkerThread);
t.Join();
}
public void WorkerThread()
{
//Do some stuff
}
You can also add a timeout as parameter to the function, but you don't need that here.

BackgroundWorker thread and Timer logic

I've been trying to get the logic right for my timer and backgroundworker thread. Granted I don't fully understand the whole system despite all my reading. the following are excerpts of code concerned:
My polling button :
private void pollStart_Click(object sender, EventArgs e)
{
tst_bgw = new BackgroundWorker();
//mandatory. Otherwise will throw an exception when calling ReportProgress method
tst_bgw.WorkerReportsProgress = true;
//mandatory. Otherwise we would get an InvalidOperationException when trying to cancel the operation
tst_bgw.WorkerSupportsCancellation = true;
tst_bgw.DoWork += tst_bgw_DoWork;
tst_bgw.ProgressChanged += tst_bgw_ProgressChanged;
tst_bgw.RunWorkerCompleted += tst_bgw_RunWorkerCompleted;
tst_bgw.RunWorkerAsync();
}
which I think is right so far
my Background worker thread:
private void tst_bgw_DoWork(object source, DoWorkEventArgs e)
{
m_timer = new System.Timers.Timer();
m_timer.Interval = 1000;
m_timer.Enabled = true;
m_timer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
if (tst_bgw.CancellationPending)
{
e.Cancel = true;
return;
}
}
and the elapsed tier event code:
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
if (powerVal > 3250)
{
m_timer.Stop();
tst_bgw.CancelAsync();
}
else
{
string pow;
int progressVal = 100 - ((3250 - powerVal) / timerVal);
uiDelegateTest tstDel = new uiDelegateTest(recvMessage);// the recvMessage function takes a textbox as an argument and directs output from socket to it.
pow = construct_command("power", powerVal);
sData = Encoding.ASCII.GetBytes(pow);
if (active_connection)
try
{
m_sock.Send(sData);
Array.Clear(sData, 0, sData.Length);
tstDel(ref unit_Output);// Read somewhere that you can only modify UI elements in this method via delegate so I think this is OK.
m_sock.Send(time_out_command);
tstDel(ref unit_Output);
tst_bgw.ReportProgress(progressVal);
}
catch (SocketException se)
{
MessageBox.Show(se.Message);
}
tst_bgw.ReportProgress(powerVal, progressVal);
powerVal = powerVal + pwrIncVal;
}
I'd just like to know a few other things; am I using the right timer (not that I think it should matter greatly but it was suggested that this might be the best timer for what I want to do) and canI really modify UI elements in the DoWork method only through delegates and if yes are there sepcial considerations to doing so.
Sorry about the long posting and thank you for your time.
There is lots wrong with this code.
1) You aren't disposing of your background worker. BackgroundWorkers must be disposed of after use. They are designed to be used as winforms components and would normally be added to a window via the designer. This will ensure it is created with the form and disposed of when the form is.
2) All you are doing in your dowork method is creating a new timer and running it. There is no point of doing this in a background worker because it will happen so quickly anyway.
3) You will recreate the timer every time you run the background worker again. But you aren't ever stopping or disposing of the old timer, you are just overwriting the member.
I recommend you get rid of the BackgroundWorker completely and just use a timer. Create the timer in the forms constructor and make sure you dispose of it in the forms dispose method. (Or use the designer to add it to the form). In the pollstart_click method just start the timer. (If you have a poll stop method, you can stop the timer in that)
You don't need both a BackgroundWorker and a Timer to accomplish your goal. From what you have posted it looks like you want to have the user click a button which starts a polling process that quits at a certian point.
Your polling model really suggests a timer would work just fine.
If you use a Timer I would Initialize the timer after the InitializeComponent() call with something like
private void InitializeTimer()
{
this.timer = new Timer();
int seconds = 1;
this.timer.Interval = 1000 * seconds; // 1000 * n where n == seconds
this.timer.Tick += new EventHandler(timer_Tick);
// don't start timer until user clicks Start
}
The button_click will simply
private void button_Click(object sender, EventArgs e)
{
this.timer.Start();
}
Then on the timer_Tick you will need to do your polling and you should be able to update your UI from there if the timer is on the UI thread like this
void timer_Tick(object sender, EventArgs e)
{
if( determineIfTimerShouldStop() )
{
this.timer.Stop();
}
else
{
// write a method to just get the power value from your socket
int powerValue = getPowerValue();
// set progressbar, label, etc with value from method above
}
}
However if the timer thread is not on the same thread as the UI you well get an exception while trying to update the UI. In that case you can use the Invoke that DataDink mentions and do something like this
void timer_Tick(object sender, EventArgs e)
{
if( determineIfTimerShouldStop() )
{
this.timer.Stop();
}
else
{
// write a method to just get the power value from your socket
int powerValue = getPowerValue();
// set a label with Invoke
mylabel.Invoke(
new MethodInvoker( delegate { mylabel.Text = "some string"; } )
);
}
}
Given the code you posted you really didn't need to do both a BackgroundWorker and a Timer, but I have had instances where I have used a BackgroundWorker to do work when a timer is called so that I could have a timer update UI periodically and have a manual button to Refresh the UI. But I wasn't updating my UI quite the way you are.
If you still have the need to do both, here is, roughly, how you can flow your app...
Create an
InitailizeBackgroundWorker() method
along with the InitializeTimer so you have
it already initalized before the
Timer fires.
Then set the Timer.Tick
to call the
BackgroundWorker.RunWorkerAsync()
Then you can do all the UI updates from within the RunWorkerAsync by
using the
BackgroundWorker.ReportProgress().

Categories