使用异步/等待时如何更好地处理已处置的控件 [英] How to better handle disposed controls when using async/await

查看:68
本文介绍了使用异步/等待时如何更好地处理已处置的控件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下在UI线程上运行的代码:

Consider this code that runs on the UI thread:

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.这是必要的,因为在长时间运行的Task上说我await.同时,用户在表单填写完成之前将其关闭. Task将完成并继续执行尝试访问已处置表单上的控件的继续操作.发生异常.

Note that every time I await I also check IsDisposed. It's necessary because say I await on a long running Task. Meanwhile the user closes the form before it completes. The Task will finish and run a continuation that attempts to access controls on a disposed form. An exception occurs.

是否有更好的方法来处理或简化此模式?我在UI代码中大量使用await,每次检查IsDisposed都是很丑陋的,如果我忘记的话,很容易出错.

Is there a better way to handle this or simplify this pattern? I use await liberally in UI code and it's both ugly to check for IsDisposed every time and error prone if I forget.

有些提议的解决方案不符合要求,因为它们会更改功能.

There are a few proposed solutions that don't fit the bill because they change functionality.

  • 防止表单关闭,直到后台任务完成

这会使用户感到沮丧.而且它还允许潜在的昂贵GUI工作发生,这浪费时间,损害性能并且不再相关.在我几乎总是做后台工作的情况下,这可能会使表单长时间关闭.

This will frustrate the users. And it also still allows potentially expensive GUI work to occur that is a waste of time, hurts performance and is no longer relevant. In the case where I'm almost always doing background work this could prevent the form close for a very long time.

  • 隐藏表单并在完成所有任务后将其关闭

这具有防止关闭表单的所有问题,除非这不会使用户感到沮丧.继续执行昂贵的GUI工作的延续将仍然运行.它还增加了跟踪所有任务完成时跟踪的复杂性,如果隐藏了窗体则将其关闭.

This has all the problems of preventing the form close except doesn't frustrate users. The continuations that do expensive GUI work will still run. It also adds complexity of tracking when all tasks complete and then closing the form if it's hidden.

  • 关闭表单时,使用CancellationTokenSource取消所有任务
  • Use a CancellationTokenSource to cancel all tasks when the form is closing

这甚至不能解决问题.实际上,我已经这样做了(也没有浪费背景资源的意思).这不是解决方案,因为由于隐式竞争条件,我仍然需要检查IsDisposed.下面的代码演示了竞争条件.

This doesn't even address the problem. In fact, I already do this (no point in wasting background resources either). This isn't a solution because I still need to check IsDisposed due to an implicit race condition. The below code demonstrates the race condition.

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的麻烦.

The race condition happens when the task has already completed, the form has closed, and the continuation still runs. You will see InvalidOperationException being thrown occasionally. Cancelling the task is good practice, sure, but it doesn't alleviate me from having to check IsDisposed.

澄清

原始代码示例完全是 我想要的功能.这只是一个丑陋的模式,执行等待后台工作然后更新GUI"是一个很常见的用例.从技术上讲,我只希望如果处理完表格就不会继续执行继续操作.示例代码只是这样做,但并不优雅,并且容易出错(如果我忘记在每个await上都检查IsDisposed,我将介绍一个错误).理想情况下,我想编写一个可以封装此基本设计的包装程序,扩展方法等.但是我想不出一种方法.

The original code example is exactly what I want in terms of functionality. It's just an ugly pattern and doing "await background work then update GUI" is a quite common use case. Technically speaking I just want the continuation to not run at all if the form is disposed. The example code does just that but not elegantly and is error prone (if I forget to check IsDisposed on every single await I'm introducing a bug). Ideally I want to write a wrapper, extension method, etc. that could encapsulate this basic design. But I can't think of a way to do this.

此外,我想我必须说性能是一流的考虑因素.例如,由于我不愿讨论的原因而引发异常非常昂贵.因此,我也不希望在执行await时仅尝试捕获ObjectDisposedException.甚至更丑陋的代码也会损害性能.似乎每次都执行一次IsDisposed检查是最好的解决方案,但我希望有更好的方法.

Also, I guess I must state performance is a first-class consideration. Throwing an exception, for example, is very expensive for reasons I won't get into. So I also don't want to just try catch ObjectDisposedException whenever I do an await. Even uglier code and also hurts performance. It seems like just doing an IsDisposed check every single time is the best solution but I wish there was a better way.

编辑#2

关于性能-是的,这都是相对的.我知道绝大多数开发人员都不在乎抛出异常的代价.引发异常的真正代价是脱离主体.在其他地方有很多可用的信息.可以说,它比if (IsDisposed)检查要贵许多个数量级.对我来说,不必要地引发异常的代价是无法接受的.我说这种情况是不必要的,因为我已经有了一个不会抛出异常的解决方案.再次,让连续数抛出ObjectDisposedException并不是可接受的解决方案,而恰好是我要避免的.

Regarding performance - yes it is all relative. I understand the vast majority of developers don't care about the cost of throwing exceptions. The true cost of throwing an exception is off-subject. There is plenty of information available on this elsewhere. Suffice to say it's many orders of magnitude more expensive than the if (IsDisposed) check. For me, the cost of needlessly throwing exceptions is unacceptable. I say needless in this case because I already have a solution that doesn't throw exceptions. Again, letting a continuation throw an ObjectDisposedException is not an acceptable solution and exactly what I'm trying to avoid.

推荐答案

在这种情况下,我还使用IsDisposed检查控件的状态.尽管它有点冗长,但处理情况并没有多余的冗长,而且一点也不令人困惑.像F#和monads这样的函数式语言可能在这里有帮助-我不是专家-但这似乎和C#一样好.

I also use IsDisposed to check the state of the control in such situations. Although it is a bit verbose, it is no more verbose than necessary to handle the situation - and it is not confusing at all. A functional language like F# with monads could probably help here - I'm no expert - but this seems as good as it gets in C#.

这篇关于使用异步/等待时如何更好地处理已处置的控件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆