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);
}
}
Related
I'm newer to the concept of threading and I would like to use Task that is a component of Thread in my application because the save task takes time for executing.
This is my code:
private void SaveItem(object sender, RoutedEventArgs e)
{
// Button Save Click ( Save to the database )
Task.Run(() =>
{
var itemsS = Gridview.Items;
Dispatcher.Invoke(() =>
{
foreach (ItemsModel item in itemsS)
{
PleaseWaittxt.Visibility = Visibility.Visible;
bool testAdd = new Controller().AddItem(item);
if (testAdd)
Console.WriteLine("Add true to Items ");
else
{
MessageBox.Show("Add failed");
return;
}
}
PleaseWaittxt.Visibility = Visibility.Hidden;
});
});
MessageBox.Show("Save Done");
// update the gridView
var results = new Controller().GetAllItems();
Gridview.ItemsSource = null;
Gridview.ItemsSource = results;
Gridview.Items.Refresh();
}
The problem is that when I save all items, I got duplicate data in the database. Otherwise, the count of ItemsS is fixed to 300, but after the saving, I got 600,
Did Task.Run() repeat the save task to the database ?
NB: I'm working on UI project ( WPF Desktop app )
I'm thinking you'd need something along the lines of this.
I quickly whipped it up but i hope its enough to attempt a fix yourself.
private async void SaveItem(object sender, RoutedEventArgs e)
{
try {
var itemsS = GridviewServices.Items.ToList(); // to list makes shallow copy
await Task.Run(() => {
foreach (ItemsModel item in itemsS)
{
bool testAdd = new Controller().AddItem(item);
}
});
// Dont update ui in task.run, because only the ui thread may access UI items
// Do so here - after the await. (or use dispatcher.invoke).
GridviewServices.Items.Clear();
GridviewServices.Items = itemsS;
} catch { ... } // Handle exceptions, log them or something. Dont throw in async void!
}
I'm also thinking this would work:
private async void SaveItem(object sender, RoutedEventArgs e)
{
// Button Save Click ( Save to the database )
var itemsS = GridviewServices.Items;
await Task.Run(() =>
{
foreach (ItemsModel item in itemsS)
{
Dispatcher.Invoke(() => {PleaseWaittxt.Visibility = Visibility.Visible;})
bool testAdd = new Controller().AddItem(item);
if (testAdd)
Console.WriteLine("Add true to Items ");
else
{
MessageBox.Show("Add failed");
return;
}
}
Dispatcher.Invoke(() => {PleaseWaittxt.Visibility = Visibility.Hidden;})
});
MessageBox.Show("Save Done");
// update the gridView
var results = new Controller().GetAllItems();
Gridview.ItemsSource = null;
Gridview.ItemsSource = results;
Gridview.Items.Refresh();
}
The problem you're running in to, is because the Task you're executing isn't running in parallel, but synchronously to the rest of your application.
When you're running CPU-intensive tasks in the background of your UI-application, you'll want to either work with actual threads or async/await - which is what you attempted with your code.
What you'll want to do is something similar to this:
private async void SaveItem(object sender, RoutedEventArgs e) => await Task.Run(
/*optionally make this async too*/() => {
// Execute your CPU-intensive task here
Dispatcher.Invoke(() => {
// Handle your UI updates here
});
});
This is just a general overview, I don't know your exact use-case, but this should get you started in the right direction.
One thing to be weary of when using Lambdas and such, is closures.
If your application tends to use a lot of memory, you might want to re-think the structure of your calltree and minimize closures in your running application.
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;
}
}
After making the project simpler, I believe I identified the problem is actually a result the async marshalling.
UPDATE: I made the code simpler to try to figure out what was going on. So here is an update... The Observable collection is being populated on a new thread (async method). I tried moving the assigning of the ItemsSource to after the ObservableCollection is loaded as seen below
async void LoadAllData(object sender, EventArgs e)
{
if (sender != null)
{
App.GeoLocationComplete -= LoadAllData;
}
await ViewModelObjects.NearbyLocations.LoadLocationData();
lvPlaces.ItemsSource = ViewModelObjects.NearbyLocations.GBSLocationDetails;
}
The definition for the data load method is a follows:
public async Task LoadLocationData()
{....}
When I run this code I get the following error:
The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))
I know what is causing the error (the data was loaded on a thread other than the UI thread) but I don't know how to fix it. Suggestions?
UPDATE UPDATE: So I believe I have identified the root cause of the problem but have not figured out how to fix it. I started by simplifying my code as follows and it worked.
public nearbyplaces()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, false);
LoadAllData(null, null);
}
void LoadAllData(object sender, EventArgs e)
{
lobj_Places = new ObservableCollection<GBSLocationDetail>()
{
new GBSLocationDetail()
{
Title = "Location 1",
Distance = "20 Miles",
AddInfo = "Something Else",
AttributesTexts="Gay, Bar, Dance"
}
};
lvPlaces.ItemsSource = lobj_Places;
}
HOWEVER, what I need is for the LoadAllData method to be called once I have the GPS location from the device. So in my App.XAML.cs I have the following delegate event declared:
public static Plugin.Geolocator.Abstractions.IGeolocator gobj_RealGeoCoordinator;
public static event GeoLocationCompleteEventHandler GeoLocationComplete;
public static bool gb_WaitingForLocation = true;
Then I have the following code call the event once I get the location back from the device:
private async void ProcessStartupandResume()
{
if (gobj_RealGeoCoordinator == null)
{
gobj_RealGeoCoordinator = CrossGeolocator.Current;
ViewModelObjects.AppSettings.CanAccessLocation = App.gobj_RealGeoCoordinator.IsGeolocationEnabled;
if (!ViewModelObjects.AppSettings.CanAccessLocation)
{
await MainPage.DisplayAlert(ResourceStrings.GetValue("NoLocationServicesTitle"), ResourceStrings.GetValue("NoLocationServicesMessage"), ResourceStrings.GetValue("OKButtonText"));
}
//Only add the events if the object has to be created.
gobj_RealGeoCoordinator.PositionChanged += gobj_RealGeoCoordinator_PositionChanged;
gobj_RealGeoCoordinator.PositionError += (sender, e) =>
{
ProcessException(new Exception(e.Error.ToString()));
};
}
//Set this to null to trigger the first check
ib_GPSReenabled = null;
if (gobj_RealGeoCoordinator.IsListening)
await gobj_RealGeoCoordinator.StopListeningAsync();
gobj_RealGeoCoordinator.DesiredAccuracy = 50;
await gobj_RealGeoCoordinator.StartListeningAsync(10000, 20);
}
private static void gobj_RealGeoCoordinator_PositionChanged(object sender, PositionEventArgs e)
{
var pos = e.Position;
ViewModelObjects.AppSettings.Latitude = pos.Latitude;
ViewModelObjects.AppSettings.Longitude = pos.Longitude;
if (gb_WaitingForLocation)
{
gb_WaitingForLocation = false;
GeoLocationComplete?.Invoke(new object() , null);
}
}
Then in my page I subscribe to the GeoLocationComplete event using the LoadAllData method as seen below. Even when I use a local object and try to set the ItemsSource for the ListView in the code when executed as a result of the event being raised, I receive the error. See code below which subscribed to the event:
public nearbyplaces()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, false);
if (App.gb_WaitingForLocation)
App.GeoLocationComplete += LoadAllData;
else
LoadAllData(null, null);
}
Any suggestions on how to fix this?
OK so I figured it out. I needed to invoke the event on the main thread and I did that with the following code:
Device.BeginInvokeOnMainThread(() =>
{
GeoLocationComplete?.Invoke(new object(), null);
});
After inserting this code, the error was gone. Changing the code back to simply
GeoLocationComplete?.Invoke(new object(), null);
cause the error to occur again. Thus I believe this resolved my problem. Hope this helps someone else. :)
I have a background worker that does some work. I want to have a spinner on main control indicating that app is working. But looks like threading is preventing my spinner from animating (sometimes it doesnt even show)... can some one explain why it is not working (probably because sleeping the thread) and perhaps guide me to a solution with minimal code changes :)
Best regards, no9.
public void StartProcess(object obj)
{
this.eventAggregator.GetEvent<ActionEvent>().Publish(new Message(EMessageType.Info)
{
Title = "Start",
Description = "Starting action..."
});
Worker = new BackgroundWorker();
Worker.DoWork += new DoWorkEventHandler(worker_DoWork);
Worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
try
{
this.Document = null;
Dictionary<string, Stream> tmp = this.GetContent();
//start and show the spinner
this.View.ShowDocumentProgressSpinner(true);
Worker.RunWorkerAsync(tmp);
}
catch (Exception ex)
{
ExceptionPolicy.HandleException(ex, "LogAndSwallow");
this.eventAggregator.GetEvent<ActionEvent>().Publish(new Message(EMessageType.Error)
{
Title = "Error",
Description = "There was an error processing your action."
});
}
finally
{
this.View.ShowActionButton(false);
}
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
var logger = new ActionLoggerAndViewUpdater(this.eventAggregator, this.View);
foreach (KeyValuePair<string, Stream> pair in (Dictionary<string, Stream>)e.Argument)
{
using (Stream stream = pair.Value)
{
//setting the document fires login event that changes stuff on presenter (current class instance)
this.Document = new Document(stream);
//check if ok to continue
while (!this.IsLoggedInForTheWorkingDocument)
//wait of the login stuff to complete
Thread.Sleep(2000);
System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
{
//this depends on the login and takes some time to process
this.DoSomeStuff();
}));
}
}
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//stop and hide the spinner
this.View.ShowDocumentProgressSpinner(false);
...
}
Why do you invoke something on your background worker? The background workers whole purpose is to not run in the UI thread and block it. Do not invoke a long running process from the worker, it defeats it's purpose.
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
var logger = new ActionLoggerAndViewUpdater(this.eventAggregator, this.View);
foreach (KeyValuePair<string, Stream> pair in (Dictionary<string, Stream>)e.Argument)
{
using (Stream stream = pair.Value)
{
this.Document = new Document(stream);
// this should REALLY be handled by an event, not busy waiting:
while (!this.IsLoggedInForTheWorkingDocument)
//wait of the login stuff to complete
Thread.Sleep(2000);
// removed the invoking, this is supposed to run in the background, right?
this.DoSomeStuff();
}
}
}
Because your worker runs in another thread, it cannot update the UI thread as it progresses, hence the behaviour. See this other, similar question Updating GUI (WPF) using a different thread.
Essentially you need to call control.Dispatcher.Invoke to get your UI to update from the other thread
May be I am wrong but my assuption is that any background thread can read and write into List or ObservableCollection if I don't care about any particular order. If I need a surtain order I will use BlockingCollection.
private void buttonTesting_Click(object sender, RoutedEventArgs e)
{
PrepareDataForTesting();
Stopwatch timer1 = new Stopwatch();
timer1.Start();
//some code preparing data
List<Task> tasks = new List<Task>();
//Testing for each pair
foreach (InterfaceWithClassName aCompound in Group1)
{
foreach (InterfaceWithClassName bCompound in Group2)
{
InstancePair pair = new InstancePair();
//some code
Task task = Task.Factory.StartNew(() => TestPairSerial(pair));
tasks.Add(task);
}
}
var ui = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.ContinueWhenAll(tasks.ToArray(),
antecedents =>
{
timer1.Stop();
TimeSpan ts1 = timer1.Elapsed;
string elapsedTime1 = String.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts1.Hours, ts1.Minutes, ts1.Seconds, ts1.Milliseconds / 10);
statusLabel_1.Content = "Elapsed time to run the test" + elapsedTime1;
statusLabel_0.Content = "Testing made " + passes + " passes";
pairsResultsDataGrid.ItemsSource = pairsResultsTable.DefaultView;
System.Media.SystemSounds.Exclamation.Play();
}, CancellationToken.None, TaskContinuationOptions.None, ui);
System.Media.SystemSounds.Beep.Play();
}
(Note: I am not sure if it matters - "pair" is found through Reflection)
When I click the button I can hear the last line - System.Media.SystemSounds.Beep.Play(); meaning we out of the event handler and all the threads are launched. But then my application is still frozen untill ContinueWhenAll is done.
TestPairSerial(pair) method has the following structure:
private void TestPairSerial(object instances)
{
do
{
do
{
//here are two methods that read data from minData ObservableCollection
//minData is a public static property of MainWindow
//minData is bound to Chart control (in XAML)
} while (isSetbCompoundParams);
} while (isSetaCompoundParams);
//filling up results into one table and two dictionaries (main window variables)
}
You are executing the tasks in the main thread. You can execute the whole code asynchronously with Dispatcher.BeginInvoke
this.Dispatcher.BeginInvoke(new Action(() => {
// your code here
}), null);