为什么无法从中关闭它给了我一条错误消息(跨线程操作无效:控制“ 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();}
答案 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!
当我看到您对我的答案做了什么之后,我感到非常震惊……看来您甚至都没有读过它!您选择了两个解决方案: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();}