考虑在UI线程上运行的以下代码:
dividends = await Database.GetDividends();
if (IsDisposed)
return;
//Do expensive UI work here
earnings = await Database.GetEarnings();
if (IsDisposed)
return;
//Do expensive UI work here
//etc...
请注意,每次我
await
时,我也会检查IsDisposed
。这是必要的,因为在运行了很长时间的await
上说了Task
。同时,用户在完成表单之前将其关闭。 Task
将完成并运行继续尝试访问已处置表单上的控件。发生异常。是否有更好的方法来处理或简化此模式?我在UI代码中自由使用
await
,每次检查IsDisposed
都很难看,如果我忘记了,很容易出错。编辑:
有一些提议的解决方案不符合要求,因为它们会更改功能。
这会使用户感到沮丧。而且,它仍然允许进行可能昂贵的GUI工作,这浪费时间,损害性能并且不再相关。在我几乎总是做后台工作的情况下,这可能会使表单长时间关闭。
除了阻止用户使用之外,这还有防止关闭表单的所有问题。继续执行昂贵的GUI工作的延续将仍然运行。它还增加了跟踪所有任务完成时跟踪的复杂性,如果隐藏了窗体则将其关闭。
CancellationTokenSource
取消所有任务这甚至不能解决问题。实际上,我已经这样做了(也没有浪费背景资源的意思)。这不是解决方案,因为由于隐式竞争条件,我仍然需要检查
IsDisposed
。下面的代码演示了竞争条件。public partial class NotMainForm : Form
{
private readonly CancellationTokenSource tokenSource = new CancellationTokenSource();
public NotMainForm()
{
InitializeComponent();
FormClosing += (sender, args) => tokenSource.Cancel();
Load += NotMainForm_Load;
Shown += (sender, args) => Close();
}
async void NotMainForm_Load(object sender, EventArgs e)
{
await DoStuff();
}
private async Task DoStuff()
{
try
{
await Task.Run(() => SimulateBackgroundWork(tokenSource.Token), tokenSource.Token);
}
catch (TaskCanceledException)
{
return;
}
catch (OperationCanceledException)
{
return;
}
if (IsDisposed)
throw new InvalidOperationException();
}
private void SimulateBackgroundWork(CancellationToken token)
{
Thread.Sleep(1);
token.ThrowIfCancellationRequested();
}
}
当任务已经完成,表单已关闭并且继续运行时,就会发生竞争状态。您会看到
InvalidOperationException
偶尔被抛出。当然,取消任务是一种好习惯,但这并不能减轻我不得不检查IsDisposed
的麻烦。澄清
就功能而言,原始代码示例正是我想要的。这只是一个丑陋的模式,执行“等待后台工作然后更新GUI”是一个很常见的用例。从技术上讲,我只希望如果处理完表格就不会继续执行继续操作。示例代码只是这样做,但并不优雅,并且容易出错(如果我忘记在每个
IsDisposed
上检查await
,我都会介绍一个错误)。理想情况下,我想编写一个可以封装此基本设计的包装程序,扩展方法等。但是我想不出办法。另外,我想我必须说性能是一流的考虑因素。例如,由于我不愿讨论的原因而引发异常非常昂贵。因此,我也不想在执行
ObjectDisposedException
时仅尝试捕获await
。甚至更丑陋的代码也会损害性能。似乎每次都执行IsDisposed
检查是最好的解决方案,但我希望有更好的方法。编辑#2
关于性能-是的,这都是相对的。我知道绝大多数开发人员都不关心抛出异常的代价。引发异常的真正代价是脱离主体。在其他地方有很多可用的信息。可以说它比
if (IsDisposed)
检查要贵许多个数量级。对我来说,不必要地引发异常的代价是无法接受的。我说这种情况是不必要的,因为我已经有了一个不会抛出异常的解决方案。再说一次,让一个连续性抛出一个ObjectDisposedException
并不是一个可以接受的解决方案,而这正是我想避免的。 最佳答案
在这种情况下,我还使用IsDisposed
来检查控件的状态。尽管它有点冗长,但处理情况并没有多余的冗长,而且一点也不令人困惑。像F#和monads这样的功能语言可能在这里有帮助-我不是专家-但这似乎和C#一样好。