【问题标题】:Waiting for a background worker to finish without blocking UI thread等待后台工作人员完成而不阻塞 UI 线程
【发布时间】:2012-11-06 15:57:05
【问题描述】:

我有一个应用程序需要等待后台工作人员完成才能继续。后台工作人员负责将一些值写入初始化它的 USB 设备。后台工作者的问题是我的主程序可以在后台工作者完成初始化之前尝试访问USB设备。

当后台工作人员工作时,UI 线程会显示“请稍候”并带有动画进度条。进度条反映后台工作人员的距离,它只是“旋转”。

我已经阅读了一些关于不要使用后台工作人员的问题,因为我真的不希望它异步运行(这是真的),但是,如果不使用后台工作人员,我的“请稍候”对话框块并且不显示动画。我还阅读了很多内容,告诉我只使用一个 UI 线程,然后支持使用后台工作线程。

我还尝试将“请稍候”微调器放在单独的线程中,但这会带来复杂性和奇怪的竞争条件,“请稍候”窗口会在打开之前尝试关闭。

解决这个问题的正确方法是什么?

【问题讨论】:

  • 您用来启动应用程序的脚本是什么?
  • 你所做的对我来说似乎是正确的选择。异步工作有什么问题?我们正在进入一个异步计算的世界。不要害怕(并使用血统库)
  • @SteveB 除了导致脚本等待运行之外,我最终没有更改任何内容。我不介意异步调用,但查找和修复竞态条件可能有点挑战

标签: c# multithreading backgroundworker


【解决方案1】:

这正是BackgroundWorker 的设计目的。尽管还有其他方法可以做到这一点,但您所做的一切都很好。

【讨论】:

  • 那我一定是做错了什么......失败的一种情况:后台工作的最后一件事是将USB设备上的值设置为'1',但我可以从后台工作人员完成并读取“0”之前的 USB 设备
  • @Azkar 你在做什么来防止用户界面在忙碌时从设备读取?您可能需要发布一些代码。
  • 如果您在上面看到,UI 确实会被禁用,但如果我使用脚本加载应用程序,脚本会在后台工作人员完成之前开始执行。
  • @Azkar 然后你需要一些东西来告诉脚本不要继续。没有代码,很难提供更多细节。简而言之,查看 BackgroundWorker 以确定它何时完成,然后让脚本继续运行。
  • 啊。看来问题不在于后台工作者和 UI 线程,而在于脚本的问题。并非每个脚本都需要连接/初始化 USB 设备,因此脚本必须知道等待设备初始化才有意义。
【解决方案2】:

我已经阅读了一些关于不要使用后台工作人员的问题

真的不知道为什么人们建议使用它,因为它正是它的设计目的。理想情况下,您想要做的是显示一个带有微调器的 modal 对话框和一个取消选项,这样用户就会被 UI 阻止中断 USB 上的写入过程。

即使只是在写入过程中禁用按钮也足够了。

【讨论】:

  • 按钮确实启用了。当我使用脚本输入启动应用程序时,就会出现问题。该脚本在后台工作人员初始化设备之前开始运行。
【解决方案3】:

我认为您正在尝试解决两个不同的问题。

第一个是如何防止主 (UI) 线程访问未初始化的 USB 设备。另一个是处理“请稍候”微调器对话框。

我的建议是:

  1. BackgroundWworker 处理USB 设备初始化。
  2. 为您的类添加一个布尔字段,以标记 USB 设备何时完成初始化 例如

    private volatile bool _usbDeviceInited = false;

  3. 后台工作人员要做的最后一件事就是将此标志设置为true

  4. 通过属性集中访问 USB 设备。如果 USB 初始化尚未完成,getter 方法可以检查布尔字段并返回空值(或者如果您愿意,可以引发异常)。
  5. 修改您的代码以检查是否由授予对 USB 设备的访问权限的属性返回 null(或处理异常,如果您选择引发一个),这意味着它尚未准备好使用。您可以处理此情况以提醒用户 USB 设备尚未准备好。

前面的步骤是解决您的第一个问题的一种方法。 现在,对于微调器。

我建议订阅BackgroundWorkerRunWorkerCompleted 事件。如果您的 BackgroundWorker 是从主 UI 线程创建的,则该事件将在主 UIThread 中触发(这意味着与用户界面元素交互是安全的)。从那里您可以简单地关闭并处理您的“请稍候”对话框。您还应该检查AsyncCompletedEventArgs.Error 属性以确保在初始化USB 设备时没有从BackgroundWorker 线程引发错误情况。

编辑: 我刚刚读到您的应用程序可能会运行一个立即尝试访问 USB 设备的脚本。使用我建议的模式,您可以在运行脚本之前“轮询”属性,直到它返回一个不同于 null 的值,如下所示:

bool timedOut = false;
DateTime timeout = DateTime.Now.AddSeconds(timeoutSeconds);
while ( MyClass.MyUsbDevice == null ) {
   if( DateTime.Now > timeout ) {
      timedOut = true;
      break;
   }
   Thread.Sleep(0); // Avoid pegging the CPU, yield it to other processes
}

if( !timedOut ) {
   // run the script
} else {
   // Handle timeout
}

祝你好运!

【讨论】:

  • 再想一想,我认为我的建议会冻结 UI 线程,即使您将它让给其他进程。
  • 如果在 WinForms 下,您可以用Thread.Sleep(0) 代替Application.DoEvents() 以防止 UI 线程冻结。这通常被视为“代码气味”。如果您在 WPF 下(或者不喜欢使用 Application.DoEvents,这确实是一个杂项),那么我建议您更改 while 循环以获取定期触发的 Timer 事件,直到 USB 属性准备好或发生超时。这将避免代码冻结并允许应用程序处理事件泵。
  • 第三个想法,@ispiro 解决方案更好,将脚本生成为 RunWorkerCompleted 事件处理程序的一部分 =)
【解决方案4】:

将必须在BackgroundWorker 完成后执行的部分(-脚本)放入BackgroundWorkerRunWorkerCompleted 事件处理程序中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多