2011-11-16 67 views
3

我一直在试图创建一个写入数据库而不阻塞UI线程的任务。我遇到的最大问题是等待该过程完成,而不发生阻塞。等待一个漫长的过程,仍然在更新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),所以它不会被锁定。

我在做什么错?

+0

欢迎(因此)。一些小事情:1。请不要在“C#.NET(3.0)”之类的东西前加上标题。那是什么标签用于[所以]。 2.请不要签名。 3.说到标签,你应该通过添加适当的标签告诉我们你是否使用winforms或wpf或其他。 –

+0

对不起,我不知道该用什么来标记它,用5个标签帽,我没有说。我正在使用Winforms(现在是一个标签) –

+0

@JohnSaunders:我强烈反对你的建议,不要在标题中包含标签(例如C#.NET WinForms)。原因:Google是人们进入翻页页面最常用的方式。拥有包含这种背景的标题使得决定问答是否相关更为迅速。 – ToolmakerSteve

回答

4

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

+0

这是否意味着我应该启动BackgroundWorker,然后保留所有代码执行直到RunWorkerCompleted触发?这并不意味着用户仍然可以与表单(与DoEvents相同)进行交互,而流程运行? 它也增加了对全局变量(如数据库连接)的需求,如果调用函数在类中,而不是在表单本身上,则需要更多的代码。 –

+0

有很多方法可以解决这个问题。简单的是禁用界面,并搭上一个微调或其他视觉。然后在异步火灾时将其撕下。其他选项只是“冻结”控件或在背景进行时以不同的方式处理响应。 – Hogan

+0

我不知道为什么这会需要更多的代码,应该是闭包应该允许你在你的线程中使用局部变量的情况。你想看到这样的例子吗? – Hogan

0

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

+0

不幸的是,我们目前无法购买任何其他组件。尽管如此,我确实喜欢他们的一些Web界面组件。 –

+0

进度组件是免费的。 – drdwilcox

0

如果您将长数据库操作委派给后台工作线程,则通过从工作人员触发ProgressChangedEvent来发送进度信息 ,您会处理这一操作,即在UI中更新进度条。同样完成的是通过发射RunWorkerCompleteEvent来发信号。

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

问题是当你的后台线程正在做什么,你不允许在窗体中做什么。

关闭它,更改编辑框?等 这是通过某种状态机完成的,可能有点像你关闭线程时禁用按钮一样简单,然后在RunWorkerCompleted事件中再次启用它。 你可以单独离开buutom并检查一个叫做繁忙的布尔值,它是Click处理程序。

您是否正在卸载进程以显示进度或者仅仅是为了避免“Windiows没有响应”,或者在中间进程期间用户可以合理执行其他操作,如关闭表单,取消操作等。

一旦你已经知道UI应该做什么,你可以将backgrondworker事件绑定到一些代码来管理事情。

+0

它主要是针对“窗口没有响应”的消息,但是在某些时候应该有一些通知给用户一个进程正在运行,所以他们不认为我们已经崩溃了,我想。 –

4

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

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

等待==阻挡

你说你想阻止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次(或更多)。

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