How update DataGrid.ItemsSource without freezes? - c#

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

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>

c# Task cancellation when using System.Timers

I'm unsure how best to cancel a task that is running a system timer.
In the code below, every 60 mins the timer will elapse and then run another method (CheckFileOverflow) that is used to check the file size of a system log txt. file
Cancellation of the timer ideally would be done by a button click or another method that calls the cancellation. The timer will effectively be allowed to run for as long as the software is running, but when the user eventually shuts down the software i'd like to be able to cancel the task in a responsible fashion i.e. not run the risk of ongoing thread pool resources lingering being used in the background.
I have spent many many hours reading up on cancellation tokens but still don't get it :(
public void SystemEventLoggerTimer()
{
SysEvntLogFileChckTimerRun = true;
Task.Run(() =>
{
System.Timers.Timer timer = new System.Timers.Timer
{ Interval = 1000 * 60 * 60 };
timer.Elapsed += new ElapsedEventHandler(CheckFileOverflow);
timer.Start();
});
}
I'd suggest that you use Microsoft's Reactive Framework (aka Rx) - just NuGet System.Reactive.
Then you do this:
IDisposable subscription =
Observable
.Interval(TimeSpan.FromHours(1.0))
.Subscribe(_ => CheckFileOverflow());
When you want to cancel the subscription just call subscription.Dispose().
Rx is ideal for abstracting away timers, events, tasks, async operations, etc.
You can change your method to something like this
public void SystemEventLoggerTimer(CancellationToken cancelToken)
{
SysEvntLogFileChckTimerRun = true;
Task.Run(async () =>
{
// Keep this task alive until it is cancelled
while (!cancelToken.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromMinutes(60));
CheckFileOverflow();
}
});
}
Then you call SystemEventLoggerTimer like this
var cancelSource = new CancellationTokenSource();
SystemEventLoggerTimer(cancelSource.Token);
you can cancel this Token when program has been disposed or simply at the end of your main function
Why not just have a timer accessible in the calling context (or globally in your class/application) - you'd have to do that with the CancellationTokenSource anyway! This doesn't look like the right use case for a Task.
Try this:
public void SystemEventLoggerTimer(System.Timers.Timer timer)
{
SysEvntLogFileChckTimerRun = true;
timer.Elapsed += new ElapsedEventHandler(CheckFileOverflow);
timer.Start();
}
Calling code:
var timer = new System.Timers.Timer() { Interval = 1000 * 60 * 60 };
SystemEventLoggerTimer(timer);
Cancellation code (in cancel button's event handler, etc):
timer.Stop();
I have posted below what appears to be a satisfactory solution which worked for me. Hopefully I'm responding to the thread in the correct manner... (a newbie to stackOverflow)
I setup a quick windows form for testing, I created 2qty buttons and 1qty textbox.
Buttons are used to Start & Stop the timer (using cancellation token)
The textbox is used to monitor the timer which will update with "Timer Running" message every 2 seconds. Hope this helps anyone else looking at a similar scenario...
enter image description here
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private CancellationTokenSource cancelSource;
// Button is used to START the timer.
private void TimerStartButton_Click(object sender, EventArgs e)
{
cancelSource = new CancellationTokenSource();
// Run the below method that will initiate timer to start running from
// the button click.
SystemEventLoggerTimer(cancelSource.Token);
}
private void SystemEventLoggerTimer(CancellationToken cancelToken)
{
Task.Run(async () =>
{
// Keep this task alive until it is cancelled
while (!cancelToken.IsCancellationRequested)
{
// Encapsulating the function Task.Delay with 'cancelToken'
// allows us to stop the Task.Delay during mid cycle.
// For testing purposes, have reduced the time interval to 2 secs.
await Task.Delay(TimeSpan.FromSeconds(2), cancelToken);
// Run the below method every 2 seconds.
CheckFileOverflow();
}
});
}
// When the below method runs every 2 secs, the UpdateUI will allow
// us to modify the textbox form controls from another thread.
private void CheckFileOverflow()
{
UpdateTextbox("Timer Running");
}
// UpdateUI will allow us to modify the textbox form controls from another thread.
private void UpdateTextbox(string s)
{
Func<int> del = delegate ()
{
textBox1.AppendText(s + Environment.NewLine);
return 0;
};
Invoke(del);
}
// Button that is used to STOP the timer running.
private void TimerStopButton_Click(object sender, EventArgs e)
{
// Initiate the cancelleation request to method "SystemEventLoggerTimer"
cancelSource.Cancel();
}
}

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

Refreshing UI every 10 s causing my app to freeze

I've created simple application that is reading data from MSSQL database every few seconds and binding datagrid with rows from db (in case there are new items in db).
In fact two applications are being used, one of them is inserting some rows to database and this app is not part of topic, and another one is displaying that rows, so if I wanted to keep second app updated all the time I'm refreshing
datagrid every 10 sec to keep it updated, but sometimes it freezes and that is really bad.. now I will explain this by image:
public partial class MainWindow : Window
{
CollectionViewSource collectionViewSource = new CollectionViewSource();
public MainWindow()
{
try
{
InitializeComponent();
//First time app is runned I'm getting all orders from DB because I want to show some items if they exist, when app is started
var ordersList = OrdersController.getAllOrders();
//This code is used just to fill my collection with ordersList when app is started so I don't need to wait for 10 sec to databind it (look below method in timer)
collectionViewSource.Source = ordersList;
collectionViewSource.GroupDescriptions.Add(new PropertyGroupDescription("OrderNo"));
DataContext = collectionViewSource;
//In this method I'm binding my datagrid with ordersList, and I'm repeating this task every 10 seconds
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(Convert.ToInt32(10));
timer.Tick += timer_Tick;
timer.Start();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
void timer_Tick(object sender, EventArgs e)
{
try
{
//Here I'm setting source to my collection/datagrid
var ordersList = OrdersController.getAllOrders();
collectionViewSource.Source = null;
collectionViewSource.Source = ordersList;
DataContext = collectionViewSource;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
// Method located in my Controller: OrdersController
public static List<Orders> getAllOrders()
{
DataServices.MyApp.Refresh(System.Data.Linq.RefreshMode.OverwriteCurrentValues, DataServices.MyApp.Orders);
var results = DataServices.MyApp.proc_Orders_GetAll().ToList();
List<Orders> orders = new List<Orders>();
foreach (var item in results)
{
LocalOrders lorders = new LocalOrders();
lorders.Title = item.Title;
lorders.Note = item.Note;
lorders.Code = Convert.ToInt32(item.Code);
lorders.CreatedAt = Convert.ToDateTime(item.CreatedAt);
orders.Add(lorders);
}
return orders;
}
I guess solution to this is to apply asynchronous task but I'm not sure, because I'm begginer and I don't have idea how this might be solved? I don't have logic of how things should work so I could choose some better path.. So any kind of help would be aswesome!
THANKS A LOT!
CHEERS!
The Tick event of a DispatcherTimer is raised on the UI thread which means that your application will be will be frozen during the time it takes to fetch the data from the OrdersController. You may try to execute the getAllOrders() on a background thread by starting a TPL task:
void timer_Tick(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
return OrdersController.getAllOrders();
})
.ContinueWith(task =>
{
if (task.Exception != null)
{
MessageBox.Show(ask.Exception.Message);
}
else
{
collectionViewSource.Source = task.Result;
DataContext = collectionViewSource;
}
}, System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}
If it is setting the Source property of the CollectionViewSource, you will have to fetch less data or try to remove the GroupDescriptions. I am afraid using GroupDescriptions won't work very well when you are displaying a lot of data.
Make the Tick handler async, and perform the long running operation in a Task:
private async void timer_Tick(object sender, EventArgs e)
{
try
{
var ordersList = await Task.Run(() => OrdersController.getAllOrders());
collectionViewSource.Source = ordersList;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}

Dispatcher.Invoke to update WPF canvas causes performance problems

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

Categories