我无法关闭此表格

时间:2019-06-02 06:47:43

标签: c# winforms timer

为什么无法从中关闭它给了我一条错误消息(跨线程操作无效:控制“ Form4”从创建该线程的线程之外的其他线程访问)

我的表单代码;

 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();}

修改 我从朋友那里得到了帮助以更改我的代码,但是from4和form6打开了很多时间。

 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();}

2 个答案:

答案 0 :(得分:2)

Winforms具有一个“拥有线程”模型。

那是什么意思?

此模型可防止您从另一个线程(而不是创建它的线程)访问UI组件。

为什么?

因为GUI组件不是线程安全的。不应这样做,因为它们会慢很多。因此,当您尝试从另一个线程(而不是拥有的线程)访问GUI组件时,WinForms会向您抛出类似的异常。

但是为什么这会发生在你身上?

因为System.Timers.Timer在自己的线程中执行其回调,该线程不是创建GUI的线程(应用程序的主线程)。因此,您不能从其回调访问任何GUI组件。

有什么解决方案?

可以使用称为 Dispatcher 的工具从另一个线程访问GUI组件。但是,如果您想要的只是一个简单的计时器,那么您将拥有更好的解决方案。

只需使用System.Windows.Forms.Timer而不是System.Timers.Timer。此计时器特定于WinForms,并为您处理与调度程序有关的所有黑色工作。 (注意:WPF具有System.Windows.Threading.DispatcherTimer出于相同的目的。)

但是,有一个陷阱:该计时器没有AutoReset属性。因此,您应该在运行一次后手动删除该事件,例如:

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

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

由于您要关闭窗户,因此并不是真正需要的,但是为了安全...

此外,请注意,您不需要同时使用Stop()Enabled = false,它们是相同的(我个人更喜欢Stop(),我认为它更具可读性)。

在您的示例中(使用AutoReset),您根本不需要Stop()-AutoReset = false仅运行一次回调。

编辑:

尽管在您的情况下不需要,但我附上了有关“如何使用调度程序”的说明。

每个WinForms的表单都有一个 Dispatcher ,以及一些与之相关的方法。最重要的是Invoke()BeginInvoke()(两个重载版本,我说的是第一个使用System.Delegate的版本)。

通过这些方法,您只能从作为参数传递的方法中从不拥有的线程中访问两个GUI组件(在大多数情况下,必须先将其强制转换为System.Delegate)。

区别在于Invoke()仅在调用方法之后返回,而BeginInvoke()是异步的;它会立即返回。

因此,您可以按照以下方式重写代码:

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();
    // }));
}

注意:切勿将长期运行的任务放在Invoke()BeginInvoke()中!由于它们是在拥有的线程中执行的-而不是在被调用的线程中执行的,因此它们将冻结GUI-根本不使用线程要容易得多...将计算放入线程中,并仅调用这些方法来更新GUI!

编辑2:

当我看到您对我的答案做了什么之后,我感到非常震惊……看来您甚至都没有读过它!您选择了两个解决方案:winforms计时器(好的)和调度程序(在本例中为bas)!简化您的Tick事件,以便:

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

此外,在您的myFunction()中,以模态形式显示您的第二种形式 。也就是说,该方法在关闭第二个窗体后将不会返回。有关更多详细信息,请参见What's the difference between Show(), ShowDialog() and Application.Run() functions?。我想您要无模显示第二个表格。

答案 1 :(得分:0)

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();}