I have a problem with async retrieving data from database, everytime i get UI lock.
private async void RetrieveHotlist(object sender, RoutedEventArgs e) //button click
{
var hotItems = new ObservableCollection<HotItem>();
await Task.Factory.StartNew(() =>
{
try
{
var serv = "xxx";
string connStr = Common.GetConStrEF(serv + "\\" + Common.DBLOGIN_INSTANCE,
Common.DBLOGIN_DBNAME, Common.DBLOGIN_USER, Common.DBLOGIN_PASSWORD);
var dataModel = new xxxxDataModel(connStr);
foreach (var category in dataModel.SpecialNumberCategory) //retrieving database CreateObjectSet<SpecialNumberCategory>("SpecialNumberCategory"); //ObjectContext
{
var item = new HotItem() { Name = category.Name };
hotItems.Add(item);
}
}
catch (Exception exception)
{
var baseException = exception.GetBaseException();
MessageBox.Show("Error\n\n" + exception.Message + "\n\n" + baseException.Message);
}
});
if (Settings != null)
{
Settings.Hotlist.Clear();
foreach (var hotItem in hotItems)
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => Settings.Hotlist.Add(hotItem)));
}
}
}
Why the RetrieveHotlist method locks my UI? And why await Task.Factory.StartNew() it's not enough?
Thanks for help :)
EDIT:
I deleted some code to be more clear.
private void RetrieveHotlist(object sender, RoutedEventArgs e) //button click
{
var b = new BackgroundWorker();
b.DoWork += (o, args) =>
{
Thread.Sleep(2000); //**UI IS FULL RESPONSIVE FOR 2 sec.**
var hotItems = new ObservableCollection<HotItem>();
try
{
var serv = "xxxx";
var dataModel = new xxxxDataModel(connStr);
var c = dataModel.SpecialNumberCategory; //**UI FREEZE / ENTITY FRAMEWORK**
b.RunWorkerCompleted += (o, args) =>
{
};
b.RunWorkerAsync();
}
EDIT2:
Thanks all for help, Entity Framework caused the issue ( i don't know why for now).
I replaced all model lines with SqlConnection and SqlCommand.Now it works great.
UI-related codes should be invoked on the UI-thread. Don't mix this with the same thread where you manipulate data (send, retrieve, update, etc.) to avoid deadlocks. On your case it was caused by the interaction with the database.
Here is an example:
Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => {
/* Your code here */
MessageBox.Show("Error\n\n" + exception.Message + "\n\n" + baseException.Message);
}));
Your method RetrieveHotlist() is blocking the UI cause below code
foreach (var hotItem in hotItems)
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => Settings.Hotlist.Add(hotItem)));
}
is getting executed on main Thread. If you see closely the problem must be your are adding item one by one to the list and every time a item is added to your Hotlist it must be raising the change to UI, even if it's not doing it on every item add event happens, it will take some time to iterate a collection and add items to another collection, which is your freeze time of UI thread.
To avoid this you can directly assign the hotItems to Hotlist( by using corresponding Ienumerable conversion). If you can give me type of Hotlist I can give you exact syntax. or you can prepare a temporary collection first of compatible type and then assign that collection to Hotlist. The key point is to minimize the work at UI thread. Replace whole foreach as below:
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => Settings.Hotlist.AddRange(hotItems)));
and move the work done by foreach in your Thread. Now in the Action , either use AddRange(), do an assignment or anything to make it work.
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 was testing a weird bug I encountered in my app, and finally was able to create a simple reproduction:
using System;
using System.Windows.Forms;
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var notifyIcon1 = new NotifyIcon();
notifyIcon1.Icon = new Form().Icon;
notifyIcon1.Visible = true;
var contextMenuStrip1 = new ContextMenuStrip();
ToolStripMenuItem menu1 = new ToolStripMenuItem();
menu1.Text = "test";
contextMenuStrip1.Items.Add(menu1);
contextMenuStrip1.Items.Add("t1");
contextMenuStrip1.Items.Add("t2");
notifyIcon1.ContextMenuStrip = contextMenuStrip1;
var timer = new System.Timers.Timer();
timer.Interval = 3000;
timer.Elapsed += (sender, e) => /* Runs in a different thread from UI thread.*/
{
if (contextMenuStrip1.InvokeRequired)
contextMenuStrip1.Invoke(new Action(() =>
{
menu1.DropDownItems.Add(e.SignalTime.ToString() + "extra");
menu1.DropDownItems.Add(e.SignalTime.ToString());
}));
else
{
menu1.DropDownItems.Add(e.SignalTime.ToString() + "extra");
menu1.DropDownItems.Add(e.SignalTime.ToString());
}
};
timer.Start();
Application.Run();
}
}
Note that the context menu will not open, but doing any of the following allows it to open:
Removing "extra" dropdown items added per execution. (To be precise
adding only 0 or 1 per execution works)
Removing part of code on InvokeRequired == false (This allows to add multiple items per execution)
Removing t1 and t2 elements. (It still works without
additional items in root)
Is this a bug or am I doing something wrong?
EDIT:
additional found condition (thanks to #derape):
It works if you move else branch to separate method, but not if you use same method in InvokeRequired branch. However using 2 method with different name and same code works.
Possible workaround could be wearing tigers skin while dancing during full-moon.
If you look at InvokeRequired then you will see there is an explicit check for IsHandleCreated which returns false. That returned value doesn't means you don't have to invoke, it simply means you can not invoke.
To confuse you even more: you must invoke, but you can't yet.
You can either decide to don't do anything if handle is not created yet (and simply miss items) or organize separate queue to store items until handle is available, similar to:
var items = new List<string>();
timer.Elapsed += (sender, e) =>
{
if (contextMenuStrip1.IsHandleCreated) // always invoke, but check for handle
contextMenuStrip1.Invoke(new Action(() =>
{
menu1.DropDownItems.Add(e.SignalTime.ToString() + "extra");
menu1.DropDownItems.Add(e.SignalTime.ToString());
contextMenuStrip1.Refresh();
}));
else
{
lock (items)
{
items.Add(e.SignalTime.ToString() + "extra");
items.Add(e.SignalTime.ToString());
}
}
};
contextMenuStrip1.HandleCreated += (s, e) =>
{
lock (items)
{
foreach (var item in items)
menu1.DropDownItems.Add(item);
contextMenuStrip1.Refresh();
}
items = null;
};
Another note: you will need to call Refresh if items were added to sub-menu, while menu is opened, but submenu is not yet, odd thing of winforms.
My problem is a synchronization problem with a thread and the user simultaneously accessing and modifying a LinkedList.
I’m making a program in C# that will display some messages in a panel. I’m getting an error called “The collection was modified after the enumerator was instantiated.”, that is because I’m adding or removing messages while a thread is accessing the LinkedList.
I have read some solutions but I am still unable to make them work. I’m using an Enumerator for the thread work in my LinkedList. I tried to make some locks in my code so the thread would not iterate the list while I remove or add an element. I also tried to lock the thread for the operations on my list. But all my attempts failed.
Here is some code of my project. This one is for adding a message:
public void addMsg(MsgForDisplay msg) {
Label lbl = new Label();
lbl.Text = (msg.getMsgText() + " -");
lbl.ForeColor = color;
lbl.Font = textFont;
lbl.BackColor = backg;
lbl.Visible = true;
lbl.AutoSize = true;
lbl.Location = new Point(width(), 0);
//lock(labels) { tried to lock here but failed
labels.AddLast(lbl);
lastLb = lbl;
this.Controls.Add(lbl);
this.Refresh();
//}
}
Removing a message:
public void removeMsg(string msg) {
string remove = msg + " -";
Label lbRemove = null;
//lock(labels) { also tried to lock here
var it = labels.GetEnumerator();
while(it.MoveNext()) {
Label label = it.Current;
if (label.Text.Equals(remove)) {
lbRemove = label;
}
}
labels.Remove(lbRemove);
this.Controls.Remove(lbRemove);
this.Refresh();
//}
}
And there is the problem, in my thread:
public void run() {
while (true) {
// lock (labels) { also tried to lock here
var it = labels.GetEnumerator();
while (it.MoveNext()) { // the crash occurs here
Label lb = it.Current;
if (lb.Location.X + lb.Width < 0) {
this.Invoke(new MethodInvoker(() => { this.Controls.Remove(lb); }));
if (labels.Count > 1)
this.Invoke(new MethodInvoker(() => { lb.Location = new Point(lastLb.Right, 0); }));
else
this.Invoke(new MethodInvoker(() => { lb.Location = new Point(2000, 0); }));
lastLb = lb;
this.Invoke(new MethodInvoker(() => { this.Controls.Add(lb); }));
this.Invoke(new MethodInvoker(() => { this.Refresh(); }));
}
if (leftLb != null)
if (leftLb.Location.X + leftLb.Width - lb.Location.X < -20)
this.Invoke(new MethodInvoker(() => { lb.Location = new Point(leftLb.Right, 0); }));
else
this.Invoke(new MethodInvoker(() => { lb.Location = new Point(lb.Location.X - 3, lb.Location.Y); }));
leftLb = lb;
}
System.Threading.Thread.Sleep(30);
// }
}
}
As you can see I’m using an GetEnumerator of my labels, what in Java should be the Iterator. With this I shouldn’t be able to iterate the list without problem when the user add or remove messages?
Is there a way to synchronize the accesses to the list?
EDIT: I have tried the ConcurrentBag and ConcurrentDictionary but without any improvement to the project as you can see in the comments…
Please before you post an answer read the comments bellow to make sure that you know what is going on.
EDIT: Tried to add a mutex to my code for addMsg and removeMsg but still crashing. If I use the mutex in the thread it will be slowed down.
I created a Timer in step of the thread and that solved the crashing problem. Here is the code if you want to see it.
private System.Windows.Forms.Timer myTimer = new System.Windows.Forms.Timer();
private void startThread() {
myTimer.Tick += new EventHandler(timerEvent);
myTimer.Interval = 30;
myTimer.Start();
}
private void timerEvent(object sender, EventArgs e) {
var it = labels.GetEnumerator();
while (it.MoveNext()) {
Label lb = it.Current;
// Label lb = labels.ElementAt(b);
if (lb.Location.X + lb.Width < 0) {
this.Controls.Remove(lb);
if (labels.Count > 1)
lb.Location = new Point(lastLb.Right, 0);
else
lb.Location = new Point(2000, 0);
lastLb = lb;
this.Controls.Add(lb);
this.Refresh();
}
if (leftLb != null)
if (leftLb.Location.X + leftLb.Width - lb.Location.X < -20)
lb.Location = new Point(leftLb.Right, 0);
else
lb.Location = new Point(lb.Location.X - 3, lb.Location.Y);
leftLb = lb;
}
}
The source of your problem is that while you are iterating over the list of labels You call either Remove or Add functions which modifies this list whis is not allowed while iterating over it. Instead of this
var it = labels.GetEnumerator();
while (it.MoveNext()) // the crash occurs here
I suggest something like that:
for(int i = 0; i < labels.Count; i++)
{
labels.remove(labels[i]); //this is valid of course the count of the list will change
//Here you can add or remove elements from the labels
}
Or you can try first to collect the removable items into a temporal list and later remove it from the original.
As others have already stated, the problem is you are modifying the collection while enumerating over it.
Now, the easiest workaround is obviously not to enumerate over the same collection that is being modified. And how do you do that? Simple, you just clone the collection, and iterate over it:
lock (labels)
{
var clone = new LinkedList<Label>(labels);
it = labels.GetEnumerator();
}
Now you can enumerate over it safely, without worrying about inconsistencies.
A few notes:
I am using a lock, because the cloning also must enumerate over your collection, and while it does it in a very short time, it is still required for synchronization. Off course, you need to uncomment the locks you've already added to addMsg and removeMsg.
The reason that locking your whole loop didn't work, is that when you call Invoke, you are essentially returning control to the thread that owns the object (the main GUI thread in this case). The problem is, that this thread is already stuck on handling whatever event caused addMsg or removeMsg to be called, leading to a deadlock.
You should also note that cloning a collection every 30 ms, isn't exactly efficient, and shouldn't be used in a production code, but given that this probably just an exercise, it should suffice. In real life, you should probably use a separate collection for the changes you are about to do (adding or removing labels), change this collection in addMsg and removeMsg, and then merge the changes to labels inside your thread, but outside of the iteration over the labels.
Not directly related to your question, but still: you should use a foreach loop instead of directly creating an enumerator object in C#.
As stated before, changing any collection while enumerating it, results in an exception in .Net. You can avoid this by using for or while loops.
However I don't see the point in using a Linked List in this scenario. It should be way simpler and more performant to use a ConcurrentDictionary and just add or remove the requested item. There is also a ObservableConcurrentDictionary available, although not part of the Framework. It is very stable, in my experience.
http://www.codeproject.com/Articles/208361/Concurrent-Observable-Collection-Dictionary-and-So
I am aware that you need to use a Dispatcher to update items in the UI thread from a worker thread. To confirm my understanding, when you get the Dispatcher associated with current object is it always the UI dispatcher if my class inherits from the UserControl class? In which cases is it not the UI dispatcher?
Anyway, in the following code, I am creating a query and starting it asynchronously and when it completes, it sets the itemsource on one of my UI elements. I also am adding items to an observable collection that a UI element uses as its itemsource. When this is ran, it works fine and isn't fussing at me to use a dispatcher and update the UI that way. Why is that?
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
QueryTask queryTask = new QueryTask(URL);
queryTask.ExecuteCompleted += new EventHandler<QueryEventArgs>(queryTask_ExecuteCompleted);
queryTask.Failed += new EventHandler<TaskFailedEventArgs>(queryTask_Failed);
Query query = new Query();
query.Where = "Field <> 'XXX'";
query.OutFields.Add("*");
queryTask.ExecuteAsync(query);
BuildingsOrganizationList.ItemsSource = organizationList;
}
void queryTask_ExecuteCompleted(object sender, QueryEventArgs e)
{
FeatureSet featureSet = e.FeatureSet;
foreach (KeyValuePair<string, string> columns in featureSet.FieldAliases)
{
TypeGrid.Columns.Add(new DataGridTextColumn()
{
Header = columns.Key,
Binding = new System.Windows.Data.Binding("Attributes[" + columns.Key + "]"),
CanUserSort = true
});
}
TypeGrid.ItemsSource = featureSet.Features;
TypeBusyIndicator.IsBusy = false;
testing();
}
private void testing()
{
List<string> temp = new List<string>();
temp.Add("Item 1");
temp.Add("Item 2");
temp.Add("Item 3");
foreach (string org in temp)
{
organizationList.Add(org);
}
}
Because even though the processing is done asynchronously, you retrieve the result in your UI thread (an event is NOT thread), and update it from there.
If, however, you put the code inside queryTask_ExecuteCompleted in a Task:
Task.Factory.StartNew(() =>
{
//code of queryTask_ExecuteCompleted here
});
You will get your exception.
The ExecuteCompleted event happens on the same thread that calls ExecuteAsync.
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);