3

我一直在尝试创建一个在不阻塞 UI 线程的情况下写入数据库的任务。我遇到的最大问题是等待该过程完成而不会发生阻塞。

我一直在尝试避免使用DoEvents(尽管现在通过这个程序非常频繁地使用它,但我想在前进的同时停止使用它)。

我试图创建进程以在第二个线程上运行并等待它完成以及使用BackgroundWorker.

我遇到的问题不是让代码在不同的线程中运行,而是试图找到一种方法来等待它完成。

基本上,现在我执行以下操作:

  1. 连接到数据库
  2. 创建一个后台工作者(或线程)来写入数据库(我可能最终会使用,BackgroundWorker所以我可以使用ReportProgress
  3. 启动线程或BackgroundWorker
  4. 使用 While 循环等待线程 /BackgroundWorker完成。对于线程,我等待IsAlive变为假,对于BackgroundWorker,我切换一个布尔变量。
  5. 我让用户知道该过程已完成。

问题出在#4。

执行一个没有代码的 while 循环,或者Thread.Sleep(0)让 UI 被阻塞(Thread.Sleep(0)使程序也占用 100% 的程序资源)

所以我这样做:

while (!thread.IsAlive)
   Thread.Sleep(1);

-或者-

while (bProcessIsRunning)
   Thread.Sleep(1);

这会阻止用户界面。

如果我Application.DoEvents()在那里调用,UI 会更新(尽管它是可点击的,所以我必须在此过程运行时禁用整个表单)。

如果我同步运行该过程,我仍然需要创建某种方式来更新 UI(在我看来,是一个DoEvents调用),因此它不会看起来被锁定。

我究竟做错了什么?

4

5 回答 5

5

首先,为什么要避免DoEvents()

其次,你使用了相互矛盾的术语。

等待 == 阻塞

你说你不想阻塞 UI 线程,但你确实想等待任务完成。这些是相互排斥的状态。如果你在等待某事完成,你就是在阻塞你的线程。

如果您希望 UI 真正可用(不被阻止),那么您不必等待任务完成。只需注册一个事件处理程序以在它完成时触发。例如,使用 BackgroundWorker,处理RunWorkerCompleted事件。对于任务,您可以使用延续将回调分派到主线程。

但似乎您只想更新 UI,而不是可用。通常,只有当您希望进度条或其他一些 UI 动画保持移动时,这才有意义。在这种情况下,我会打开一个模式对话框,启动我的任务,然后等待它,是的,调用 DoEvents()。

    var dialog = new MessageBoxFormWithNoButtons("Please wait while I flip the jiggamawizzer");
    dialog.Shown += (_, __) =>
        {
            var task = Task.Factory.StartNew(() => WriteToDatabase(), TaskCreationOptions.LongRunning);
            while (!task.Wait(50))  // wait for 50 milliseconds (make shorter for smoother UI animation)
                Application.DoEvents(); // allow UI to look alive
            dialog.Close();
        }

    dialog.ShowDialog();

模态对话框阻止用户做任何事情,但任何动画仍然可以工作,因为 DoEvents() 每秒被调用 20 次(或更多)。

(您可能希望为不同的任务完成状态添加特殊处理,但这是题外话。)

于 2011-11-16T20:06:14.747 回答
4

C# 使用事件模型——您要做的是分派完成工作的进程,然后让该进程在完成时触发自定义事件或使用其中一个线程事件。当进程在“后台”运行时,将控制从您的代码释放回系统。

于 2011-11-16T19:24:32.687 回答
0

您不能等待 UI 线程。

相反,将处理程序添加到Exitedevent

于 2011-11-16T19:24:06.387 回答
0

我不知道这是否会简化您的问题,但我们使用基本对象控件:http ://www.essentialobjects.com/Products/EOWeb/来管理我们的长期流程。

于 2011-11-16T19:26:23.523 回答
0

如果您将长 db 操作委托给后台工作线程,则通过从工作人员触发 ProgressChangedEvent 来发出进度信号,您可以处理更新 UI 中的进度条。通过触发 RunWorkerCompleteEvent 发出同样完成的信号。

无需轮询/循环执行所需的事件。

问题是当你的后台线程正在做一些你不允许在表单中做的事情时。

关闭它,更改编辑框?等等。这是通过某种状态机完成的,就像您在启动线程时禁用按钮然后在 RunWorkerCompleted 事件中再次启用它一样简单。你可以不理会buutom,并在它的Click 处理程序中检查一个名为busy 的布尔值。

您是卸载进程以显示进度还是只是为了避免“Windows 没有响应”,或者用户在进程中可以明智地做其他事情,例如关闭表单、取消操作等。

一旦你弄清楚 UI 应该做什么,你就可以将 backgrondworker 事件挂钩到一些将管理事物的代码中。

于 2011-11-16T20:00:30.677 回答