Reputation: 11
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(":[email protected]") ||
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();
}
}
Upvotes: 0
Views: 226
Reputation: 13986
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:
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. 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)
.
Upvotes: 1