Dispatcher.Invoke to update WPF canvas causes performance problems - c#

I am using Websockets to draw data on my canvas:
webSocket.OnMessage += (sender, e) =>
{
String websocketData = e.Data.Substring(3);
WebSocketDataStructure jsonData = JsonConvert.DeserializeObject<WebSocketDataStructure>(websocketData);
// Give control back to main thread for drawing
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Background,
new Action(() => this.updateCanvas(jsonData)));
};
private void updateCanvas(WebSocketDataStructure myData)
{
CanvasUtils.DrawLine(MyCanvas, colorNormalBrush, myData.hsLine.x1, myData.hsLine.y1, myData.hsLine.x2, myData.hsLine.y2);
}
When I get multiple messages per second the application starts to lag. I read that using Dispatcher.BeginInvoke() is bad for handling frequent data, since we immediately switch back to the UI-Thread every time.
Is there a better way to implement this? I thought about creating a timer and updating the UI-Thread every full second. This would work by storing websocket data in a List, and process it on the UI-Thread (https://en.wikipedia.org/wiki/Producer%E2%80%93consumer_problem). My only problem with this approach was, that I couldn't set up an endless loop with Thread.Sleep(1000) on the UI-Thread.

You could queue your high-frequent data and read items from the data queue at a lower pace. By using a DispatcherTimer you could avoid the need for directly calling the Dispatcher.
var queue = new ConcurrentQueue<WebSocketDataStructure>();
webSocket.OnMessage += (s, e) =>
{
var websocketData = e.Data.Substring(3);
var jsonData = JsonConvert.DeserializeObject<WebSocketDataStructure>(websocketData);
queue.Enqueue(jsonData);
};
var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
timer.Tick += (s, e) =>
{
WebSocketDataStructure data;
while (queue.TryDequeue(out data))
{
CanvasUtils.DrawLine(MyCanvas, colorNormalBrush,
data.hsLine.x1, data.hsLine.y1, data.hsLine.x2, data.hsLine.y2);
}
};
timer.Start();

Related

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);
}

How update DataGrid.ItemsSource without freezes?

i try to update DataGrid.ItemsSource without freezes
if have:
public static DataTable DataTableAccounts { get; set; }
Which i get from DataBase (SQLite)
To display this data in the program, I write
DataGridAccounts.ItemsSource = DataTableAccounts.DefaultView;
After changing the data in the DataTableAccounts, i update DataGrid
DataGridAccounts.ItemsSource = null;
DataGridAccounts.ItemsSource = DataTableAccounts.DefaultView;
But i do that every 1 sec, because data in DataTableAccounts is changing so fast. And because of this update i get freezes window programm.
Questions:
How i can update DataGridAccounts.ItemsSource without freezes?
p.s. I try to use (async\aswait)... ItemsSource={Binding} in XAML code... and other. Nothing helped me.
You are working too hard. You need to simply set the data grid items source to the data table.
DataGridAccounts.ItemsSource = DataTableAccounts;
As the data table changes the grid will update.
Following example run background service every 10 seconds to update GUI. You can modify it as you wish. By running your thread as async task your GUI never get hang.
public frm_testform()
{
InitializeComponent();
dispatcherTimer_Tick().DoNotAwait();
}
private async Task dispatcherTimer_Tick()
{
DispatcherTimer timer = new DispatcherTimer();
TaskCompletionSource<bool> tcs = null;
EventHandler tickHandler = (s, e) => tcs.TrySetResult(true);
timer.Interval = TimeSpan.FromSeconds(10);
timer.Tick += tickHandler;
timer.Start();
while (true)
{
tcs = new TaskCompletionSource<bool>();
await Task.Run(() =>
{
// Run your background service and UI update here
await tcs.Task;
}
}

Code should be executed one time after short delay

I have this Timer:
Timer delayTimer = new Timer();
delayTimer.Interval = 500;
delayTimer.Elapsed += (object sender, ElapsedEventArgs e) => {
Console.WriteLine("test");
textInputDialog.Show();
delayTimer.Stop();
};
delayTimer.Start();
Here I have the following problems:
Timer never stops. Code is executed every 500ms.
textInputDialog.Show(); doesn't work (perhaps cause of problem above)
What is wrong with my code?
Alternative solutions:
This is an alternative to timer as Jens Horstmann mentioned. And this is called on the UI thread:
private async Task SendWithDelay()
{
await Task.Delay(500);
textInputDialog.Show();
}
Another alternative would be NSTimer:
NSTimer.CreateScheduledTimer(new TimeSpan(0,0,0,0,500), delegate {
textInputDialog.Show();
});
And to invoke a call on the UI thread you can use InvokeOnMainThread:
Timer delayTimer = new Timer();
delayTimer.Interval = 500;
delayTimer.Elapsed += (object sender, ElapsedEventArgs e) => {
delayTimer.Stop();
Console.WriteLine("test");
InvokeOnMainThread (() => {
textInputDialog.Show();
});
};
delayTimer.Start();
Stop the timer before you show the dialog:
delayTimer.Elapsed += (object sender, ElapsedEventArgs e) => {
delayTimer.Stop();
Console.WriteLine("test");
textInputDialog.Show();
};
Also you probably used the wrong timer. Don't use System.Threading.Timer or System.Timers because this involves multithreading which does not work well with winforms or WPF. (This is probably the reason your MessageBox does not show - its called on the wrong thread)
In WPF you should use System.Windows.Threading.DispatcherTimer
Edit
In Winforms you should use System.Windows.Forms.Timer (see comments)
Here is a solution without async/await
It also happen to fit in a single statement, which is rather elegant.
This is a C#, cross-platform solution to perform an action after a delay.
Also works for a recurring task.
using System.Threading;
var delayTimer = new Timer((state) => // Create timer, forget about it
InvokeOnMainThread(() => // Fire on main thread
textInputDialog.Show() // Your code goes here
),
null, // Ignore the state
5 * 1000, // 5 seconds until the 1st fire
Timeout.Infinite); // Do not repeat
Something like this worked for me:
private async Task DelayedShow()
{
await Task.Delay(500);
await _loadPop.textInputDialog.Show();
}
Remember to call the method like this:
BeginInvokeOnMainThread(() => DelayedShow());

Calling code multi-able time using Threading?

Please see the following code:
foreach(string url in urls)
{
//Method that will process url ProcessUrl(url)
//Add eached proccessed url to a treelist
}
ProcessUrl method have HttpWebRequest and HttpWebResponse so sometime it takes a nudge and if there were many links it will take time which will hang my program.
I can't actually suggest a solution of think of one, because i may based it on something wrong so what i want is to make this code runs while i can operate 100% in my program without any crashes or hangs, and that each newly processed link will be inserted to the treelist without any lag.
If you want to perform a long-running operation in the background and pass the results of the operation back to the UI as they become available, while at the same time the UI stays responsive, then it's straightforward to use BackgroundWorker here.
void BeginExpensiveOperation()
{
var worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += ExpensiveWork;
worker.ProgressChanged += WorkerOnProgressChanged;
List<string> urls = new List<string> { "http://google.com" };
worker.RunWorkerAsync(urls);
}
// runs in a worker thread
void ExpensiveWork(object sender, DoWorkEventArgs e)
{
var worker = (BackgroundWorker)sender;
var urls = (List<string>) e.Argument;
foreach (var url in urls)
{
//TODO: do your work here synchronously
var result = new WebClient().DownloadString(url);
//TODO: pass the result in the userState argumetn of ReportProgress
worker.ReportProgress(0, result); // will raise worker.ProgressChanged on the UI thread
}
}
private void WorkerOnProgressChanged(object sender, ProgressChangedEventArgs progressChangedEventArgs)
{
//this executes on the UI thread
var value = progressChangedEventArgs.UserState;
//TODO: use result of computation to add it to the UI
panel.Children.Add(new TextBlock {Text = value.ToString()});
}
Fill in your problem-specific code in the //TODO: placeholders and call BeginExpensiveOperation() to start the operation asynchronously.

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.

Categories