使用 async/await 时如何更好地处理已释放的控件

2024-02-25

考虑在 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将完成并运行尝试访问已处理表单上的控件的延续。发生异常。

有没有更好的方法来处理这个问题或简化这个模式?我用awaitUI 代码中的大量内容,而且检查起来都很丑陋IsDisposed每次都这样,如果我忘记了,就很容易出错。

EDIT:

有一些建议的解决方案不符合要求,因为它们改变了功能。

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

这会让用户感到沮丧。而且它仍然允许进行潜在昂贵的 GUI 工作,这些工作浪费时间、损害性能并且不再相关。在我几乎总是做后台工作的情况下,这可能会阻止表单关闭很长一段时间。

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

这具有阻止表单关闭的所有问题,除了不会让用户感到沮丧。执行昂贵的 GUI 工作的延续仍然会运行。它还增加了跟踪所有任务完成时的复杂性,然后在表单隐藏时关闭表单。

  • Use a 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.

澄清

原始代码示例是exactly我想要的功能。这只是一个丑陋的模式,“等待后台工作然后更新 GUI”是一个非常常见的用例。从技术上讲,我只想在表单被处理后根本不运行延续。示例代码就是这样做的,但不够优雅,并且容易出错(如果我忘记检查IsDisposed在每一个await我正在引入一个错误)。理想情况下,我想编写一个包装器、扩展方法等来封装这个基本设计。但我想不出办法来做到这一点。

另外,我想我必须声明性能是首要考虑因素。例如,抛出异常的成本非常高,原因我不会详细说明。所以我也不想只是尝试捕捉ObjectDisposedException每当我做一个await。甚至更丑陋的代码也会损害性能。看起来就像只是做了一个IsDisposed每次检查是最好的解决方案,但我希望有更好的方法。

EDIT #2

关于性能 - 是的,这都是相对的。我知道绝大多数开发人员并不关心抛出异常的成本。抛出异常的真正成本是题外话。其他地方有大量关于此的信息。可以说它比普通的要贵很多数量级if (IsDisposed)查看。对我来说,不必要地抛出异常的成本是不可接受的。在这种情况下我说没必要,因为我已经有了一个不会抛出异常的解决方案。再次,让延续抛出一个ObjectDisposedException不是一个可接受的解决方案并且exactly我试图避免什么。


我也用IsDisposed在这种情况下检查控件的状态。虽然它有点冗长,但并不比处理情况所需的冗长多——而且一点也不混乱。像带有 monad 的 F# 这样的函数式语言可能会有所帮助 - 我不是专家 - 但这看起来和 C# 中的一样好。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 async/await 时如何更好地处理已释放的控件 的相关文章

随机推荐

  • 如何在 lambda 本身中获取 C++ lambda 函数的地址?

    我试图弄清楚如何获取 lambda 函数本身的地址 这是示例代码 std cout lt lt Address of this lambda function is gt lt lt 我知道我可以捕获变量中的 lambda 并打印地址 但我
  • 如何在通知区域显示进度条?

    你好 我正在做一个 Android 应用程序 在其中我将视频上传到 PHP 服务器 我在用HTTPURLConnection进行上传 我陷入了在通知区域显示进度条并更新它的困境 我搜索了近一周的时间来做到这一点 但找不到提示 如果有人知道
  • 修复 WCF 4.0 REST 的 XmlDictionaryReaderQuotas 最大长度配额 [已关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 如果 POST 正文长度超过 819
  • Safari 在重定向/表单提交时暂停所有动画

    我有一个点击链接时触发的动画 这是一个放大 div 然后淡出的 jQuery 动画 为了确保速度 在单击链接的同时 会触发重定向 这是必须发生的 我不能将重定向放在 jQuery 的 animate 的成功函数中 此重定向是通过表单提交完成
  • Django manage.py runserver 抛出 ImportError: AdminMediaHandler

    问题 当我跑步时python manage py runserver我收到错误ImportError cannot import name AdminMediaHandler ex venv agconti agconti Inspiron
  • 在远程节点上的 ssh 命令中使用 SED

    我编写了一个脚本来 ssh 到某些节点并运行sed节点内的命令 脚本看起来像 NODES compute 0 3 for i in NODES do echo i ssh i sed i s 172 16 48 70 172 20 54 1
  • Spark from_json 也不例外

    我正在使用 Spark 2 1 scala 2 11 我想将具有定义模式的 json 格式字符串从一个数据帧加载到另一个数据帧中 我尝试了一些解决方案 但最便宜的是标准列函数 from json 我尝试了一个例子 https jacekla
  • AWS Lambda 处理来自 DynamoDB 的流

    我正在尝试创建一个消耗 dynamoDB 表中的流的 lambda 函数 但是我想知道处理在执行过程中可能因某些错误而未处理的数据的最佳实践是什么 例如 我的 lambda 失败并且丢失了部分流 这是重新处理丢失数据的最佳方法 这是为你处理
  • Ruby 中的波形可视化

    我即将启动一个记录和编辑音频文件的项目 并且我正在寻找一个好的库 最好是 Ruby 但会考虑 Java 或 NET 以外的任何库 来实现波形的动态可视化 有谁知道我应该从哪里开始搜索 有大量数据要流入浏览器 Flash 或 Flex 图表可
  • 有没有办法转换客户 ID 及其最近的订单日期?

    我有一个查询 它为我提供了所有客户及其最后三个订单日期 EX CustomerId DateOrdered 167 2006 09 16 01 25 38 060 167 2006 09 21 13 11 53 530 171 2006 0
  • 默认按钮大小?

    如何创建按钮控件 使用CreateWindow of a BUTTON窗口类 是否具有与其他 Windows 应用程序一致的标准系统范围尺寸 尤其是高度 我当然应该考虑 DPI 和可能的其他设置 Remark Using USE CW DE
  • Gitolite 3 - 设置仓库描述(未授权)

    我正在使用 Gitolite 3 6 ssh git host info hello you this is got Git running gitolite3 v3 6 1 6 gdc8b590 on git 1 7 10 4 R W m
  • 阻止 ScrollView 将焦点设置在 EditText 上

    Android 的 ScrollView 当滚动或滑动时 喜欢在 EditText 是其子级之一时设置其焦点 当您滚动然后释放时会发生这种情况 有什么办法可以阻止这种行为吗 我几乎尝试了所有我能想到的以及我在 StackOverflow 上
  • iOS 14 Widget 闪烁或冻结,chronod 崩溃

    我已经为我的应用程序实现了一个小部件扩展 即 iOS 14 小部件 使用 WidgetKit 通常 该小部件工作得很好 但有时它会进入反复闪烁并最终冻结的状态 我尝试将调试器连接到小部件进程 但它没有捕获任何崩溃或异常 然而 我确实在我的设
  • 如何在 C 预处理器中生成错误或警告?

    我有一个必须仅在调试模式下编译的程序 测试目的 如何让预处理器阻止在 RELEASE 模式下进行编译 放置在任何地方 ifndef DEBUG error Only Debug builds are supported endif 以供参考
  • 如何在 Xamarin.Forms 中使用 ToolBarItems 的本机平台图标(以编程方式)?

    我拥有的 我有一个Xamarin Forms应用程序与ToolBar ActionBar在安卓上 Navigation Bar在 iOS 上 内ToolBar我有一个ToolBarItem删除某些东西 我想要的是 For the ToolB
  • 仅从 Bash 脚本中的路径获取文件名 [重复]

    这个问题在这里已经有答案了 如何只获取没有扩展名和路径的文件名 以下内容没有给我扩展名 但我仍然附加了路径 source file filename no ext source file 许多类 UNIX 操作系统都有一个basename出
  • VS Code 显示“mvn”未被识别为内部或外部命令

    我正在尝试使用 VS Code 创建一个 Maven 项目 但是当我运行命令时 它显示 mvn is not recognized as an internal or external command operable program or
  • 如何在Python中为接口分配IP地址?

    我有 python 脚本为我的无线和有线接口设置 IP4 地址 到目前为止 我使用subprocess命令如下 subprocess call ip addr add local 192 168 1 2 24 broadcast 192 1
  • 使用 async/await 时如何更好地处理已释放的控件

    考虑在 UI 线程上运行的这段代码 dividends await Database GetDividends if IsDisposed return Do expensive UI work here earnings await Da