在C#中,等待主线程继续处理UI更新? (.NET 2.0 CF)

我想以其他方式阻止主线程上的代码执行,同时仍然允许显示UI更改。

我试图想出一个我想要做的简化示例版本; 这是我能想到的最好的。 显然它没有certificate我想要的行为,或者我不会发布这个问题。 我只是希望它提供一些代码上下文来支持我对我希望解决的问题的不良解释。

在表单上的按钮单击处理程序中,我有:

private void button2_Click(object sender, EventArgs e) { AutoResetEvent autoResetEvent = new AutoResetEvent(false); new Thread(delegate() { // do something that takes a while. Thread.Sleep(1000); // Update UI w/BeginInvoke this.BeginInvoke(new ThreadStart( delegate() { this.Text = "Working... 1"; this.Refresh(); Thread.Sleep(1000); // gimme a chance to see the new text })); // do something else that takes a while. Thread.Sleep(1000); // Update UI w/Invoke this.Invoke(new ThreadStart( delegate() { this.Text = "Working... 2"; this.Refresh(); Thread.Sleep(1000); // gimme a chance to see the new text })); // do something else that takes a while. Thread.Sleep(1000); autoResetEvent.Set(); }).Start(); // I want the UI to update during this 4 seconds, even though I'm // blocking the mainthread if (autoResetEvent.WaitOne(4000, false)) { this.Text = "Event Signalled"; } else { this.Text = "Event Wait Timeout"; } Thread.Sleep(1000); // gimme a chance to see the new text this.Refresh(); } 

如果我没有在WaitOne()上设置timout,应用程序将在Invoke()调用上死锁。


至于我为什么要这样做,我的任务是移动应用程序的一个子系统在后台线程中工作,但仍然只是有时阻止用户的工作流程(主线程)和某些类型的仅与该子系统相关的工作。

你想使用“ BackgroundWorker ”类,这将为你带来大部分的痛苦……但如前所述,你还需要构建它,以便主线程更新UI而工作者是做重物。

你可能会认为它比较简单。

建议:当你需要一个线程来执行一些偶尔的工作时,从线程池中获取它,这样你就不需要奇怪/容易出错的回收代码。

当你想要另一个线程上的东西更新你的UI时,你只需要一个对表单的引用,并调用Form.Invoke传递你希望主线程执行的UI代码; 在一个事件中,它是一个最好的pactice,以尽快释放UI线程。

即:

 private void button1_Click(object sender, EventArgs e) { // this is the UI thread ThreadPool.QueueUserWorkItem(delegate(object state) { // this is the background thread // get the job done Thread.Sleep(5000); int result = 2 + 2; // next call is to the Invoke method of the form this.Invoke(new Action(delegate(int res) { // this is the UI thread // update it! label1.Text = res.ToString(); }), result); }); } 

希望这可以帮到你:)

编辑:对不起,我没有读过“阻止用户工作流程”部分。

WindowsForms不是为此而设计的,阻塞主线程是BAD(它处理来自OS的消息)。

您不必通过冻结表单来阻止用户工作流程(这将被Windows视为“无响应”),阻止用户工作流程的方法是禁用您想要的任何控件(使用上面的Invoke方法,如果来自另一个线程),甚至整个表格!!

“阻止”主线程的常见活动是打开消息框或modal dialog。 主代码似乎在MessageBox或ShowDialog调用时阻塞。

这些项目的工作方式(以及MessageBox只是一个专门的modal dialog)是它们在阻塞时包含自己的消息泵。

虽然这是一个令人讨厌的黑客攻击,你可以在应用程序中通过循环调用Application.DoEvents()来保持用户消息在你等待其他任务完成时保持抽样。 你需要小心,因为各种令人讨厌的东西可能会引发这样的消息 – 例如有人关闭表单或重新输入你当前的消息处理程序 – 模式对话框通过有效地禁用启动它们的表单输入来避免这种情况。

我的意思是说,如果你能使它适合,BackgroundWorker是一个更好的解决方案。 我有时将它与模态“进度对话框”结合起来,给我后台线程/消息泵送和阻止UI线程。

编辑 – 扩展最后一位:

我使用的一种方法是拥有一个’progress form’类,它将BackgroundWorker对象作为构造函数参数,并包含传递给它的后台worker的progress和completion事件的处理程序。

希望完成工作的表单创建后台工作程序并挂接“工作”事件(不记得现在调用它),然后创建一个进程对话框,它将后台工作程序传递给后台工作程序。 然后它以模态方式显示进度对话框,这意味着它将等待(但是消息消息),直到进度对话框关闭。

进度表单负责从其OnLoad覆盖启动BackgroundWorker,并在看到BackgroundWorker完成时自行关闭。 显然,您可以在进度表单中添加消息文本,进度条,取消按钮等。

构建您的应用程序,以便主线程仅执行UI更新,所有其他工作通过工作队列在辅助线程上完成; 然后在主线程中添加一个waiting-for-godot标志,并使用它来保护将项添加到工作队列的方法

出于好奇:你为什么要这样做?

您可能应该像其他人建议的那样重新构建代码,但是根据您正在寻找的行为,您可能还想查看在后台工作线程上使用Thread.Join。 Join实际上允许调用线程在等待另一个线程完成时处理COM和SendMessage事件。 这似乎在它的情况下可能是危险的,但我实际上有几个场景,它是等待另一个线程干净地完成的唯一方法。

Thread .. ::。Join方法

阻塞调用线程直到线程终止,同时继续执行标准COM和SendMessage抽取。

(来自http://msdn.microsoft.com/en-us/library/95hbf2ta.aspx )

我同意建议您使用后台工作者的其他人。 它完成了繁重的工作,并允许用户界面继续。 您可以使用后台工作程序的报告进度来启动主窗体在后台执行操作时可以设置为禁用的时间,然后在“某些实例”完成处理后重新启用。

如果这有帮助,请告诉我! JFV

如果您可以调整代码,以便在进程开始后设置标记,然后在开始执行其他操作之前在UI中检查,我认为您可以更轻松地对此进行编码。 我将创建一个委托,可以从线程池中的线程或用户创建的线程调用,以更新UI中的进度。 后台进程完成后,切换标志,现在可以继续正常的UI操作。 您需要注意的唯一警告是,当您更新UI组件时,您必须在创建它们的主线程/ UI线程上执行此操作。 为了实现这一点,您可以在该线程上的任何控件上调用Invoke()方法,并向其传递您需要调用它的委托和参数。

这是我前一段时间写的关于如何使用Control.Invoke()的教程的链接:

http://xsdev.net/tutorials/pop3fetcher/

只是一个代码片段:没有太多时间抱歉:)

  private void StartMyDoSomethingThread() { Thread d = new Thread(new ThreadStart(DoSomething)); d.Start(); } private void DoSomething() { Thread.Sleep(1000); ReportBack("I'm still working"); Thread.Sleep(1000); ReportBack("I'm done"); } private void ReportBack(string p) { if (this.InvokeRequired) { this.Invoke(new Action(ReportBack), new object[] { p }); return; } this.Text = p; } 

最好是派遣工作,但如果你必须,可能是这样的。 只需调用此方法等待信号而不是调用waitone。

 private static TimeSpan InfiniteTimeout = TimeSpan.FromMilliseconds(-1); private const Int32 MAX_WAIT = 100; public static bool Wait(WaitHandle handle, TimeSpan timeout) { Int32 expireTicks; bool signaled; Int32 waitTime; bool exitLoop; // guard the inputs if (handle == null) { throw new ArgumentNullException("handle"); } else if ((handle.SafeWaitHandle.IsClosed)) { throw new ArgumentException("closed wait handle", "handle"); } else if ((handle.SafeWaitHandle.IsInvalid)) { throw new ArgumentException("invalid wait handle", "handle"); } else if ((timeout < InfiniteTimeout)) { throw new ArgumentException("invalid timeout <-1", "timeout"); } // wait for the signal expireTicks = (int)Environment.TickCount + timeout.TotalMilliseconds; do { if (timeout.Equals(InfiniteTimeout)) { waitTime = MAX_WAIT; } else { waitTime = (expireTicks - Environment.TickCount); if (waitTime <= 0) { exitLoop = true; waitTime = 0; } else if (waitTime > MAX_WAIT) { waitTime = MAX_WAIT; } } if ((handle.SafeWaitHandle.IsClosed)) { exitLoop = true; } else if (handle.WaitOne(waitTime, false)) { exitLoop = true; signaled = true; } else { if (Application.MessageLoop) { Application.DoEvents(); } else { Thread.Sleep(1); } } } while (!exitLoop); return signaled; } 

我选择了尚未发布的东西,即使用MessageQueues。

  • MainThread在等待队列中的下一条消息时阻塞。
  • 后台线程将不同类型的消息发布到MessageQueue。
  • 一些消息类型通知MainThread更新UI元素。
  • 当然,有一条消息告诉MainThread停止阻塞和等待消息。

考虑到Windows消息循环已经存在于某个地方,似乎在顶部,但它的工作原理。