// Script Explanation
I'm trying to create a sort of timer which counts using a loop and a counter integer on startup, which saves the amount of seconds to a .txt file and stops when the program stops.
Because I'm using a while loop for the counting I have to do that in a separate thread to not stall the UI from updating and therefore count inside the separate thread and then invoke the text to the UI function.
The script will be at the bottom however thought I'd include this explanation for easier understanding.
// Issue
This all works fine, it updates the text label to show the seconds elapsed just fine, however when trying to close the application it throws an exception which I tried to fix by putting a bool check on when it should be counting and should not be counting inside the while loop instead of just running the while loop when true is true. I activate the bool at the start of the script when defining it and I disable it on the Form1_FormClosing event, but it still throws the following exception:
System.InvalidOperationException: 'Invoke or BeginInvoke cannot be called on a control until the window handle has been created.'
// My Script
using System;
using System.Windows.Forms;
using System.Threading;
using System.Diagnostics;
using System.IO;
using System.Reflection.Emit;
namespace PC_Timer
{
public partial class Form1 : Form
{
private bool Counting = true;
private int count = 0;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
if (File.Exists("count.txt"))
{
string text = File.ReadAllText("count.txt");
count = int.Parse(text);
label1.Text = count.ToString() " Seconds";
}
else
{
File.WriteAllText("count.txt", "0");
}
new Thread(Loop).Start();
}
private void Loop()
{
while (Counting)
{
count ;
label1.Invoke((MethodInvoker)delegate {
label1.Text = count.ToString() " Seconds";
});
Thread.Sleep(1000);
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
Counting = false;
File.WriteAllText("count.txt", count.ToString());
}
}
}
// Final Remarks
Maybe there is a better way to go about this, than doing it in a while loop since they are pretty inefficient however this is supposed to be a simple program for solo use purposes, which is why I went about doing it with a while loop because its what I'm most comfortable with.
I also tried to check if IsHandleCreated was true, which did work but I don't think it dealt with the exception, since I could not publish it because of one or multiple errors whchi Visual Studio was unable to determine the cause of.
Anyways what should I be doing different for the Invoke function to not get stuck when exiting the program?
CodePudding user response:
This is a classic race condition. The loop has already checked that Counting
is true
and is about to call Invoke()
when the main UI thread closes the form, and thus the Invoke()
fail.
It's actually more difficult to fix that one might think. An obvious idea would be to use a lock to prevent the thread loop from trying to invoke while the form is closing, but that's likely to cause deadlock if the Form1_FormClosing() is blocked waiting for the lock when the loop is trying to call Invoke().
Have you considered using a Timer to update the label instead of a background thread? That would avoid the problem