等待很长的过程并仍在更新UI

时间:2011-11-16 19:22:21

标签: c# .net winforms multithreading doevents

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

我一直在努力避免使用DoEvents(虽然现在通过此程序经常使用它,但我希望在向前移动时不再使用它。)

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

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

基本上,现在我做以下事情:

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

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

    所以我这样做:

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

    -OR -

    while (bProcessIsRunning)
       Thread.Sleep(1);
    

    阻止了用户界面。

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

    如果我同步运行该进程,我仍然需要创建某种方式来更新UI(在我看来,DoEvents调用)所以它似乎没有被锁定。

    我做错了什么?

5 个答案:

答案 0 :(得分:5)

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

其次,您使用了冲突的条款。

等待==阻止

您说想要阻止UI线程,但您执行想要等待任务完成。这些是相互排斥的状态。如果你等待完成某件事,你就会阻止你的线程。

如果您希望用户界面实际上可用(未阻止),那么您就不会等待完成任务。只需注册一个事件处理程序即可在其完成时触发。例如,使用BackgroundWorker处理RunWorkerCompleted事件。对于任务,您可以使用continuation将回调分派给主线程。

但似乎你只是想要更新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次(或更多)。

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

答案 1 :(得分:4)

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

答案 2 :(得分:0)

你不能在UI线程上等待。

而是向Exited event添加处理程序。

答案 3 :(得分:0)

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

答案 4 :(得分:0)

如果将long db操作委托给后台工作线程,那么 通过从worker触发ProgressChangedEvent来指示进度,您可以处理更新UI中的进度条。 通过触发RunWorkerCompleteEvent发出相同的完成信号。

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

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

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

您是在卸载流程以显示进度还是仅仅是为了避免“Windiows没有响应”,或者是否存在用户在中间过程中可以理解的其他事情,例如关闭表单,取消操作等。

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