C# 关闭窗体时ObjectDisposedException
我在WinForm上有一个计时器,在窗体加载时启动该计时器:C# 关闭窗体时ObjectDisposedException,c#,winforms,C#,Winforms,我在WinForm上有一个计时器,在窗体加载时启动该计时器: private void MainForm_Load(object sender, EventArgs e) { Action action = () => lblTime.Text = DateTime.Now.ToLongTimeString(); Task task = new Task(() => { while (true) { I
private void MainForm_Load(object sender, EventArgs e)
{
Action action = () => lblTime.Text = DateTime.Now.ToLongTimeString();
Task task = new Task(() => {
while (true)
{
Invoke(action);
Task.Delay(1000);
}
});
task.Start();
}
问题是当我在VS中以调试模式启动应用程序并关闭它时。我得到一个ObjectDisposedException,它声明我的表单已经被释放
我尝试用以下方法修复它:
private bool _runningTimer = true;
public MainForm()
{
InitializeComponent();
// ...
FormClosing += MainForm_FormClosing;
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
_runningTimer = false;
}
private void MainForm_Load(object sender, EventArgs e)
{
Action action = () => lblTime.Text = DateTime.Now.ToLongTimeString();
Task task = new Task(() => {
while (_runningTimer)
{
Invoke(action);
Task.Delay(1000);
}
});
task.Start();
}
public MainForm()
{
InitializeComponent();
cts = new CancellationTokenSource();
Load += MainForm_Load;
FormClosing += MainForm_FormClosing;
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
cts.Cancel();
}
private void MainForm_Load(object sender, EventArgs e)
{
CancellationToken ct = cts.Token;
Action action = () => { lblTime.Text = DateTime.Now.ToLongTimeString(); };
var task = Task.Factory.StartNew(async () => {
ct.ThrowIfCancellationRequested();
while (true)
{
Invoke(action);
await Task.Delay(100);
}
}, ct);
}
但问题仍然存在。我做错了什么
更新:我知道WinForms有一个标准计时器,它在多线程环境中工作得非常好。我只是想知道如何才能让它更好地理解如何应对比赛条件。这种计时器只是一个例子,它可能是另一个需要更新GUI的进程
更新2:以下是我找到的代码和答案:
private void MainForm_Load(object sender, EventArgs e)
{
Action action = () => { lblTime.Text = DateTime.Now.ToLongTimeString(); };
Task task = new Task(async () => {
while (!IsDisposed)
{
Invoke(action);
await Task.Delay(1000);
}
});
task.Start();
}
但是无论如何,如果我设置时间间隔,例如100ms,ObjectDisposedException仍然抛出
这不是真实的示例,我只是在尝试…在您的第一个示例中,任务不知道您的应用程序正在退出,并且在标签销毁后有调用操作的风险,因此出现了
ObjectDisposedException
尽管在第二个示例中尝试向任务发出警报,但它实际上并不是线程安全的,并且在释放控件后仍然可以调用该操作
计时器
更好的解决方案是只使用WinForms计时器。如果您通过设计器将计时器放置在表单上,它会自动将其注册为组件依赖项,从而使生命周期管理更容易
使用WinForm计时器,您不需要担心线程或任务
s,更重要的是,如果您需要更新UI(与子线程或非UI上下文任务相反),您不需要担心调用
告诉我更多
好的,我尝试通过以下方式使用任务取消:
private bool _runningTimer = true;
public MainForm()
{
InitializeComponent();
// ...
FormClosing += MainForm_FormClosing;
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
_runningTimer = false;
}
private void MainForm_Load(object sender, EventArgs e)
{
Action action = () => lblTime.Text = DateTime.Now.ToLongTimeString();
Task task = new Task(() => {
while (_runningTimer)
{
Invoke(action);
Task.Delay(1000);
}
});
task.Start();
}
public MainForm()
{
InitializeComponent();
cts = new CancellationTokenSource();
Load += MainForm_Load;
FormClosing += MainForm_FormClosing;
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
cts.Cancel();
}
private void MainForm_Load(object sender, EventArgs e)
{
CancellationToken ct = cts.Token;
Action action = () => { lblTime.Text = DateTime.Now.ToLongTimeString(); };
var task = Task.Factory.StartNew(async () => {
ct.ThrowIfCancellationRequested();
while (true)
{
Invoke(action);
await Task.Delay(100);
}
}, ct);
}
不知道它是否正确,但即使时间间隔设置为10毫秒,它似乎也能工作。问题是,在调用调用(操作)
和调用将回调消息发布到消息队列之间,表单已关闭。这是一个经典的比赛条件。不幸的是,这很难可靠地修复。您可能必须捕获并忽略该特定异常(抖动)。请注意,在mainformclosing()
中,您不能做任何聪明的事情,比如尝试等待正在进行的调用完成,因为这样您可能会导致死锁。我的第一个想法是:显示日期时间不会在尝试时造成严重破坏。忽略捕获。但是考虑使用一个计时器(事件驱动)来更新你的控制。你需要在关闭窗体之前停止这些任务,或者你的任务必须意识到窗体被关闭/关闭。问题是,您仍然试图在表单上调用(操作)
,在表单关闭后,这只是一个简单的时间问题。由于您没有将\u runningTimer
设置为volatile,编译器可以对此进行优化,但即使将其设置为volatile,也可能无法保证其正常工作。是的,基于窗口的计时器可能是最好的方法。您知道什么是任务。延迟(1000)代码>是什么?没有什么。它不会等一秒钟。您必须等待任务。延迟(1000)代码>或Task.WaitAll(Task.Delay(1000))代码>“如果窗口正在关闭,则不会触发”-严格来说,这不是真的。如果计时器已连接到窗体组件,则当窗体关闭时,计时器将关闭,从而阻止其启动。如果窗体关闭,计时器就不会开火,这并没有什么神奇之处。