考虑在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#一样好。

    07-24 14:45