LinkedList modified, Thread crashing the program - c#

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

Related

Prevent hanging in windows forms

I'm having a problem.
So I've built an app which displays data in the form of chart and a datagridview. They are both responsive. That means they rescale and move with the data. It takes some computation power I guess.
At the same time I have timers cause it all runs periodically with f=4Hz.
And now: When I run the app and switch on the periodical readout the app hangs during resizing. How could I prevent it?
I've already tried to use a backgroundworker, but the problem occurs in the moment of accessing to the datagridview and chart which are declared (and also used) in the "other thread" (as the VS said)
So.. How could I prevent it?
Maybe I should utilise the backgroundworker in the other way?
My attempts with the backgroundworker:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//Control.CheckForIllegalCrossThreadCalls = false;
if (!GetConnectionStatus())
{
stop_ticking();
if (MessageBox.Show("Device not connected", "Connection status", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error) == DialogResult.Retry)
messaging();
else
return;
}
// TEMP READ
Read_temp(tlist);
float[] t = new float[3];
float[] r = new float[3];
float[] av = new float[1];
float[] st = new float[1];
// TEMP IMPORT
tlist.Give_current_temp(t, r, av, st);
string time_stamp = tlist.Give_current_time();
rows_nr++;
// ADDING TO GRID
dataGridView1.Invoke(new Action(() => { dataGridView1.Rows.Add(new object[] { rows_nr, time_stamp, av[0], st[0], (t[0]).ToString(), (r[0]).ToString(), (t[1]).ToString(), (r[1]).ToString(), (t[2]).ToString(), (r[2]).ToString() }); }));
//dataGridView1.Rows.Add(new object[] { rows_nr, time_stamp, av[0], st[0], (t[0]).ToString(), (r[0]).ToString(), (t[1]).ToString(), (r[1]).ToString(), (t[2]).ToString(), (r[2]).ToString() });
dataGridView1.Invoke(new Action(() => { dataGridView1.FirstDisplayedScrollingRowIndex = dataGridView1.RowCount - 1; }));
//dataGridView1.FirstDisplayedScrollingRowIndex = dataGridView1.RowCount - 1;
// ADDING TO CHART
for (int i = 0; i < 3; i++)
chart1.Invoke(new Action(() => { chart1.Series[series_names[i]].Points.AddXY((rows_nr), (t[i])); }));
//chart1.Series[series_names[i]].Points.AddXY((rows_nr), (t[i]));
chart1.Invoke(new Action(() => { chart1.Series["average"].Points.AddXY((rows_nr), (av[0])); }));
//chart1.Series["average"].Points.AddXY((rows_nr), (av[0]));
//chart1.Series["std1"].Points.AddXY((rows_nr), (av[0] + Math.Abs(st[0])));
//chart1.Series["std2"].Points.AddXY((rows_nr), (av[0] - Math.Abs(st[0])));
// MOVING CHART
if (chart1.Series[series_names[0]].Points.Count > nr_of_noints_graph)
{
for (int i = 0; i < 3; i++)
chart1.Series[series_names[i]].Points.RemoveAt(0);
chart1.Series["average"].Points.RemoveAt(0);
//chart1.Series["std1"].Points.RemoveAt(0);
//chart1.Series["std2"].Points.RemoveAt(0);
chart1.ChartAreas[0].AxisX.Minimum = rows_nr - (nr_of_noints_graph - 1);
chart1.ChartAreas[0].AxisX.Maximum = rows_nr;
dataGridView1.Rows.RemoveAt(0);
}
chart1.Invoke(new Action(() => { chart1.ChartAreas[0].RecalculateAxesScale(); }));
//chart1.ChartAreas[0].RecalculateAxesScale();
}
Please take a look at background worker sample. You are doing it wrong. Background worker DoWork should not call UI controls and is executed in non UI thread, it should execute time consuming computing and call worker.ReportProgress(). While ReportProgress method can access UI controls and code in this method is executed in UI thread.
Some chart controls are lugging when adding/removing points. Maybe it hangs because it lugs. Make updates less frequently (1 in 1 second for example) and see whether it hangs or not.
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.backgroundworker?view=netframework-4.7.2
Wrap operations in Stopwatch and use System.Diagnostics.Debug.WriteLine to trace execution flow and time spent on the operations.
Moving chart part does not work because it accesses UI elements in non ui thread without Invoke to UI thread.
If it was not Background worker I would write it this way:
// MOVING CHART
chart1.Invoke(new Action(()=>
{
if (chart1.Series[series_names[0]].Points.Count > nr_of_noints_graph)
{
for (int i = 0; i < 3; i++)
chart1.Series[series_names[i]].Points.RemoveAt(0);
chart1.Series["average"].Points.RemoveAt(0);
chart1.ChartAreas[0].AxisX.Minimum = rows_nr - (nr_of_noints_graph - 1);
chart1.ChartAreas[0].AxisX.Maximum = rows_nr;
}
}
));
I wouldn't wrap each operation in separate Invokes as well.
As for your question it's insufficient information to detect what is wrong please provide minimum viable runnable sample which demonstrates the problem.
As #Access Denied states you should improve separation between GUI and Background worker threads. You could execute // TEMP READ and // TEMP IMPORT operations on background thread and make a call to the GUI thread via .Invoke method when all the data is ready. Read "How to: Make Thread-Safe Calls to Windows Forms Controls" article for more information.
When you add/update data in your DataGridView use .BeginUpdate/.EndUpdate methods to prevent control update until all the data is refreshed.
Other approach is to use Virtual mode. It's especially usefull if you have many items in grid.
When working with a background thread you must not create, update, or even access any UI element.
You need to separate the work that retrieves your data (the slow part) from the work that updates the chart (which is very fast).
It really comes down to doing it like this:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
if (!GetConnectionStatus())
{
stop_ticking();
return;
}
// TEMP READ
Read_temp(tlist);
float[] t = new float[3];
float[] r = new float[3];
float[] av = new float[1];
float[] st = new float[1];
// TEMP IMPORT
tlist.Give_current_temp(t, r, av, st);
string time_stamp = tlist.Give_current_time();
rows_nr++;
chart1.Invoke(new Action(() =>
{
// ADDING TO GRID
dataGridView1.Rows.Add(new object[] { rows_nr, time_stamp, av[0], st[0], (t[0]).ToString(), (r[0]).ToString(), (t[1]).ToString(), (r[1]).ToString(), (t[2]).ToString(), (r[2]).ToString() });
dataGridView1.FirstDisplayedScrollingRowIndex = dataGridView1.RowCount - 1;
// ADDING TO CHART
for (int i = 0; i < 3; i++)
{
chart1.Series[series_names[i]].Points.AddXY((rows_nr), (t[i]));
}
chart1.Series["average"].Points.AddXY((rows_nr), (av[0]));
// MOVING CHART
if (chart1.Series[series_names[0]].Points.Count > nr_of_noints_graph)
{
for (int i = 0; i < 3; i++)
{
chart1.Series[series_names[i]].Points.RemoveAt(0);
}
chart1.Series["average"].Points.RemoveAt(0);
chart1.ChartAreas[0].AxisX.Minimum = rows_nr - (nr_of_noints_graph - 1);
chart1.ChartAreas[0].AxisX.Maximum = rows_nr;
dataGridView1.Rows.RemoveAt(0);
}
chart1.ChartAreas[0].RecalculateAxesScale();
}));
}
If you have to show a MessageBox then you also need to invoke that.

ContexMenuStrip oddly-specific weird behaviour

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.

Avoid UI Lockups when doing lots of PropertyUpdates

I am trying to discover the best way of avoiding UI-Lockups when you are doing a lot of updates to the UI at once.
The basic premise is that on start up my tool runs a perforce FSTAT on the within a background worker. This generates a very large list of files and their information. Once this is completed, in its RunWorkerCompleted function, I then propagate this information to the UI inside of a TreeView.
This however, involves lots of property updates! Depending on the number of files that its propagating to. It can be 5000+ files. This completely locks up the UI for about 3-5 seconds.
I was wondering if I can asynchronously update the UI, such that I say, propagate 10-20 files at once & Still let the UI thread continue to update so that its still responsive.
Thank you.
If you are updating information inside of the TreeView using property bindings you could set your Binding.IsAsync flag to true. If you aren't updating the values using bindings then that might be something to look into.
Binding.IsAsync Property
Another option would be to update all your properties but, to not call the PropertyChanged event for the property (Assuming you are using INotifyPropertyChanged to update your bindings) until all your data has been changed and then call the PropertyChanged event for each of your properties on a Task so, it is still Async but even with 5000+ binding updates it should not take 3-5 seconds.
lots of suggestions you made finally got me to a good answer. Here is my code below. Basically we can use ReportProgress to allow the UI to update-while-running. Then adjust for how often we want this to happen. Here is my solution below.
The key is that PropegateMetaData is called for every N number of items (I specified 25). Then the list is emptied.
This will call report progress for every 25 items, then continue on. And eventually pass the rest to WorkerCompleted.
public static void Refresh(List<string> refreshPaths)
{
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
List<string> filesPath = null;
if (refreshPaths == null)
{
filesPath = DatabaseViewModel.Instance.Records.Select(record => record.Filepath).ToList();
}
else
{
filesPath = new List<string>(refreshPaths);
}
if (m_Repository != null && filesPath.Count > 0)
{
IList<FileSpec> lfs = new List<FileSpec>();
int index = 0;
foreach (DataRecord rec in DatabaseViewModel.Instance.Records)
{
lfs.Add(new FileSpec(new LocalPath(rec.Filepath), null));
index++;
if (index > MaxFilesIteration)
{
GetFileMetaDataCmdOptions opts = new GetFileMetaDataCmdOptions(GetFileMetadataCmdFlags.AllRevisions, null, null, 0, null, null, null);
worker.ReportProgress(0, m_Repository.GetFileMetaData(lfs, null));
lfs.Clear();
index = 0;
}
}
args.Result = m_Repository.GetFileMetaData(lfs, null); //pass the remaining results across
}
};
worker.ProgressChanged += (sender, args) => PropegateMetaData(args.UserState as IList<FileMetaData>);
worker.RunWorkerCompleted += (sender, args) => PropegateMetaData(args.Result as IList<FileMetaData>);
worker.RunWorkerAsync();
}
private static void PropegateMetaData(IList<FileMetaData> fileList)
{
IList<FileMetaData> fileState = fileList as IList<FileMetaData>;
if (fileState != null)
{
foreach (FileMetaData fmd in fileState)
{
DataRecord currentRecord = DatabaseViewModel.Instance.GetRecordByFilepath(fmd.LocalPath.Path);
if (currentRecord != null)
{
switch (fmd.Action)
{
case FileAction.Add:
currentRecord.P4Status = P4FileState.Added;
break;
case FileAction.Edit:
currentRecord.P4Status = P4FileState.Edit;
break;
case FileAction.MoveAdd:
currentRecord.P4Status = P4FileState.MoveAdd;
break;
default:
currentRecord.P4Status = P4FileState.None;
break;
}
}
}
}
}

Cross-thread operation not valid. Multiple solutions fail to work,

Before reading, I want everyone reading this to know that I have tried multiple delegate/Cross-Threading/Invoking Solutions from all over stack overflow.
With that said, this is what my program is supposed to do:
Worker Thread 1 is called to start Async Operation.
If it detects a line that has a typical PRIVMSG header along with the word subscribed!
Create a new MetroTaskWindow with a TaskWindowControl and Add it to the queue
Worker Thread 2 is called after worker thread 1
Worker Thread 2 checks every 5 seconds if queue contains something
If it does, show it and get rid of it
Here is the associated Code If you need more, let me know to the above requirements:
Worker Thread 1 Segment
string line = "";
while (!backgroundWorker1.CancellationPending)
{
try
{
line = reader.ReadLine();
}
catch { }
if (line != null && !line.Contains("JOIN"))
{
try
{
if (line.Contains("PING") && !line.Contains("PRIVMSG"))
{
writer.Write(line.Replace("PING", "PONG"));
Trace.WriteLine(line.Replace("PING", "PONG"));
}
else if (line.Split(new char[] { ' ' })[0].Equals(":twitchnotify!twitchnotify#twitchnotify.tmi.twitch.tv") ||
line.Split(new char[] { ' ' })[0].Equals(":stds_catchemall!stds_catchemall#stds_catchemall.tmi.twitch.tv") && line.Contains("subscribed!"))
{
total += 1;
checkNotifications();
}
}
catch
{
continue;
}
}
if (!String.IsNullOrEmpty(line))
Trace.WriteLine(line);
}
}
private void checkNotifications()
{
List<Achievement> tempQueue = new List<Achievement>();
foreach (Achievement a in achievements) {
//I know i could shorten this, but i need it left like this...
if (a.AfterSub)
tempQueue.Add(a);
if (total - a.Goal == start)
tempQueue.Add(a);
if (total == a.Goal)
tempQueue.Add(a);
}
foreach (Achievement a in Sort(tempQueue))
{
MetroTaskWindow m = new MetroTaskWindow(a, this, a.Type.ToString(), new TaskWindowControl(a.Name, a.Message, a), 4, r, ((ScreenRegion)r).getGS());
queue.Add(m);
}
}
Worker Thread 2
private void CheckAvailable_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
Thread.Sleep(5250);
BeginInvoke((MethodInvoker)delegate
{
if (queue.Count > 0)
{
queue[0].Show(); // <---- Error Occurs Here
//Cross-thread operation not valid: Control 'TaskWindowControl' accessed from a thread other than the thread it was created on.
queue.RemoveAt(0);
}
});
}
}
Metro Task Window
public MetroTaskWindow(Achievement a, IWin32Window parent, string title, Control userControl, int secToClose, MetroForm r, Form gs)
{
controlContainer = new MetroPanel();
Controls.Add(controlContainer);
controlContainer.Controls.Add(userControl);
userControl.Dock = DockStyle.Fill;
closeTime = secToClose * 500;
this.a = a;
form = r;
chroma = gs;
p = (Form1)parent;
this.Text = title;
this.Resizable = false;
this.Movable = true;
this.StartPosition = FormStartPosition.Manual;
if (parent != null && parent is IMetroForm)
{
this.Theme = ((IMetroForm)parent).Theme;
this.Style = ((IMetroForm)parent).Style;
this.StyleManager = ((IMetroForm)parent).StyleManager.Clone(this) as MetroStyleManager;
this.ShadowType = MetroFormShadowType.None;
}
switch (a.Type)
{
case PopupType.Achievement:
Text = "Achievement!";
break;
case PopupType.Milestone:
Text = "Milestone!";
break;
case PopupType.Notification:
Text = "Notification";
break;
}
}
TaskWindowControl
public partial class TaskWindowControl : UserControl
{
public TaskWindowControl(string name, string info, Achievement a)
{
InitializeComponent();
metroLabel1.Text = name;
metroTextBox1.Text = info;
metroTextBox1.Select(0, 0);
try
{
Trace.WriteLine(Directory.GetCurrentDirectory() + "\\" + a.Picture);
pictureBox1.Image = Image.FromFile(Directory.GetCurrentDirectory() + "\\" + a.Picture);
}
catch
{
MessageBox.Show("There was an error loading the image for this achievement.");
}
}
}
And as I stated above, there are a LOT of duplicates, none of which have helped my answer. I also don't know much about the Delegate/Invoking process which is why I need some extra help.
Update #1
Anywhere I had queue.Add(m); is now replaced with queue.Enqueue(a);
And my update method (Without timer so far) is just:
public void DisplayDialog()
{
Achievement a = null;
queue.TryDequeue(out a);
MetroTaskWindow m = new MetroTaskWindow(a, this, a.Type.ToString(), new TaskWindowControl(a.Name, a.Message, a), 4, r, ((ScreenRegion)r).getGS());
m.Show();
}
Something that I found is that when I changed the code to this, the Thread that does the animations and things on my MetroTaskWindow doesn't get activated. The Windows Stays in with a windows loading circle and it never goes away. Any ideas? I'm using the OnActivated event, so it SHOULD fire when i .Show();
Edit #2
What I ended up doing to get my above error to work, was to switch all of my code to a new Windows Form Timer. This polls every 5 seconds, and keeps the MetroTaskWindow from hanging due to the while loop.
Form1.Designer.cs
private System.Windows.Forms.Timer timer2;
timer2 = new System.Windows.Forms.Timer(this.components);
timer2.Interval = 5250;
timer2.Tick += new System.EventHandler(this.timer2_Tick);
Form1.cs
private void timer2_Tick(object sender, EventArgs e)
{
if (queue.Count > 0)
{
DisplayDialog();
}
}
The error makes sense. The queue contains MetroTaskWindow instances created on a worker thread, not on the UI thread. You call checkNotifications in the background worker's entry point method, which runs on a separate thread.
What I recommend you do is:
Store in the queue only metadata about the windows. Create a new class with name, message and anything MetroTaskWindow needs to be created. Add instances of this class to the queue in checkNotifications. By the looks of it you might be able to use the Achievement class directly and push that to the queue.
Create the windows and show them in CheckAvailable_DoWork, where you now call queue[0].Show();.
After you solve this you should post a new question about how to refactor your code into async/await and get rid of that ugly background worker.
A quick refactoring idea
I would remove the second background worker altogether and use a timer instead that polls the queue every X seconds (or 5250ms, if you prefer).
To make this work you need to change the queue's type which is now actually a List<T> to a ConcurrentQueue<T>. This will allow you to push stuff from the worker, and pop from the UI thread (in the timer callback).
This way you can remove the second background worker and that while(true).

Closing or Hiding forms causes a cross thread error

I am baffled by this simple task i do over and over again.
I have an array of child forms. The array is initiated in another form's constructor:
frmChildren = new ChildGUI[20];
When the user requests to see a child form, i do this:
if (frmChildren[nb] == null)
{
frmChildren[nb] = new ChildGUI();
frmChildren[nb].MdiParent = this.MdiParent;
}
frmChildren[nb].Show();
So far this works. In the background i can download new content for these forms. When a download is finished i fire a ChildChange event. Here is where it stops working.
I simply want to close/hide any forms open then regenerate a new set of -frmChildren = new ChildGUI[20];- here is one of many trials:
for (int i = 0; i < frmChildren.Length;i++ )
{
if (frmChildren[i] != null)
{
//frmChildren[i].BeginInvoke(new EventHandler(delegate
//{
frmChildren[i].Close();
//}));
}
}
frmChildren= new ChildGUI[20];
I get a Cross Thread exception on the .Close(). Notice i've already tried doing an invoke, but doing so bypasses the !=null for some reason. I think it may have something to do with the garbage collector. Anybody have an input?
The problem is that your anonymous method is capturing i - so by the time it's actually invoked in the UI thread, you've got a different value of i, which may be null. Try this:
for (int i = 0; i < frmChildren.Length; i++)
{
ChildGUI control = frmChildren[i];
if (control != null)
{
control.BeginInvoke(new EventHandler(delegate
{
control.Close();
}));
}
}
frmChildren = new ChildGUI[20];
See Eric Lippert's blog post for why introducing a new variable within the loop fixes the problem.
EDIT: If you want to use a foreach loop, it would look like this:
foreach (ChildGUI control in frmChildren)
{
// Create a "new" variable to be captured
ChildGUI copy = control;
if (copy != null)
{
copy.BeginInvoke(new EventHandler(delegate
{
copy.Close();
}));
}
}
frmChildren = new ChildGUI[20];
Just as an aside, you can use the fact that you just want to call a void method to make the code slightly simpler. As this no longer uses an anonymous method, you can make do away with the "inner" variable:
foreach (ChildGUI control in frmChildren)
{
if (control != null)
{
control.BeginInvoke(new MethodInvoker(control.Close));
}
}
frmChildren = new ChildGUI[20];

Categories