简体   繁体   中英

I can't close this form

why I can't close this from it gave me an error message (Cross-thread operation not valid: Control 'Form4' accessed from a thread other than the thread it was created on)

my form code;

 System.Timers.Timer t = new System.Timers.Timer();

private void Form4_Load(object sender, EventArgs e)
    { myFunction2();}
private void myFunction2()
    {
        t.Interval = int.Parse(textBox1.Text);
        t.Elapsed += T_Elapsed;
        t.Start();
        t.AutoReset = false;

    }
 private void T_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {

        myFunction();

        t.Stop();
        t.Enabled = false;
        this.Close();
    }
private void myFunction()
    {


        var form6 = new Form6();
        //form6.Closed += (s, args) => this.Close();
        form6.ShowDialog();}

Edit I get help from a friend to change this in my code but still, the from4 open and form6 open much time.

 private System.Windows.Forms.Timer t = new System.Windows.Forms.Timer();

private void myFunction2()
    {
         t.Interval = int.Parse(textBox1.Text);
        t.Tick += T_Elapsed;
        t.Start(); 
    }
 private void T_Elapsed(object sender, EventArgs e)
    {
        myFunction();
        this.Invoke((new Action(() => 
                       this.Close();
        }))); 
    }
 private void myFunction()
    {
        Form6 form6 = new Form6();
        form6.ShowDialog();}

Winforms has an "owning-thread" model.

What does that mean?

This model prevents you from accessing an UI component from another thread, not the one which created it.

Why?

Because GUI components are not thread-safe. and should not be, since they'll be much slower. So, WinForms throws an exception like that at you when you try to access a GUI component from another thread - not the owned thread .

But why does this happen toyou?

Because System.Timers.Timer executes its callback in its own thread, which isn't the thread that created the GUI (the main thread of the app). So, you can't access from its callback to any GUI component.

What's the solution?

You can access an GUI component from another thread by a tool called a Dispatcher . But if all you want is a simple timer, you have nicer solution.

Simply use System.Windows.Forms.Timer instead of System.Timers.Timer . This timer is specific to WinForms, and handles all the black work with the dispatcher for you. (Note: WPF has System.Windows.Threading.DispatcherTimer for the same purpose).

But, there's one pitfall: this timer has not AutoReset property. So, you should remove the event by hand after one run, like:

private void T_Elapsed(object sender, EventArgs e)
{
    myFunction();

    t.Stop();
    this.Close();
}

Since you're closing the window, this is not really needed, but for safety...

Also, note that you don't need both Stop() and Enabled = false together, they are identical (I personally prefer Stop() , I think it's more readable).

In your example (with AutoReset ) you didn't need Stop() at all - AutoReset = false run the callback only one time.

Edit:

Although it isn't needed in your case, I append an explanation about "how to use the dispatcher".

Each WinForms' form has a Dispatcher , and some methods related to it. The most important are Invoke() and BeginInvoke() (two overloaded versions, I'm talking about the first which takes System.Delegate ).

These methods enable you two access GUI components from not-owning thread, only from the method passed as parameter (in most cases, you must cast it to System.Delegate first).

The difference is, that Invoke() returns only after the method called, while BeginInvoke() is asynchronous; it returns immediately.

So, you can rewrite you code as follows:

private System.Timers.Timer t = new System.Timers.Timer();

public Form1()
{
    InitializeComponent();

    t.Elapsed += T_Elapsed;
    t.Interval = int.Parse(textBox1.Text);
    t.AutoReset = false;
    t.Start();
}

private void T_Elapsed(object sender, EventArgs e)
{
    this.Invoke((Action)(() => // You can use `BeginInvoke()` as well
    {
        this.Close();
    }));
    // Or
    // this.Invoke(new Action(() => // You can use `BeginInvoke()` as well
    // {
    //     this.Close();
    // }));
}

Note: Never put long-running tasks inside Invoke() or BeginInvoke() ! since they're executed in the owning thread - not in the called thread, they'll freeze the GUI - it's much easier to not use threads at all... Put the calculations in the thread, and call these methods only to update the GUI!

Edit 2:

After I saw what you did with my answer, I was shocked... It seems you even didn't read it! You chose both the solutions: the winforms timer (the good), and the dispatcher (the bas, in this case)! simplify you Tick event so:

private void T_Elapsed(object sender, EventArgs e)
{
    myFunction();
    Close();
}

Also, in your myFunction() , you show your second form in modal form . That say, that the method won't return after the second form is closed. See What's the difference between Show(), ShowDialog() and Application.Run() functions? for more details. I think you want to show your second form modeless.

private System.Windows.Forms.Timer t = new System.Windows.Forms.Timer();
private void T_Elapsed(object sender, EventArgs e)
    {
        if (true)
        {
            myFunction();
            t.Enabled = false;
            t.Stop();
        }
    }
 private void myFunction2()
    {
        t.Interval = int.Parse(textBox1.Text);
        t.Tick += T_Elapsed;
        t.Start();

    }
    private void myFunction()
    {
        t.Enabled = false;
        t.Stop();
        this.Hide();
        Form6 form6 = new Form6();
        form6.ShowDialog();}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM