在 C# 中,在继续处理 UI 更新的同时等待主线程?(.NET 2.0 CF)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/360789/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
In C#, wait on the mainthread while continuing to process UI updates? (.NET 2.0 CF)
提问by CrashCodes
I want to otherwise block code execution on the main thread while still allowing UI changes to be displayed.
我想以其他方式阻止主线程上的代码执行,同时仍然允许显示 UI 更改。
I tried to come up with a simplified example version of what I'm trying to do; and this is the best I could come up with. Obviously it doesn't demonstrate the behavior I'm wanting or I wouldn't be posting the question. I just hope it gives some code context to back my poor explanation of the problem I'm hoping to solve.
我试图想出一个我正在尝试做的事情的简化示例版本;这是我能想到的最好的方法。显然它没有表现出我想要的行为,否则我不会发布问题。我只是希望它提供一些代码上下文来支持我对我希望解决的问题的糟糕解释。
Within a button click handler on a form I have this:
在表单上的按钮单击处理程序中,我有这个:
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();
}
If I didn't set a timout on the WaitOne() the app would deadlock on the Invoke() call.
如果我没有在 WaitOne() 上设置超时,应用程序将在 Invoke() 调用上死锁。
As to why I'd want to do this, I've been tasked with moving one subsystem of an app to do work in a background thread, but still have it block user's workflow (the main thread) only sometimes and for certain types of work related to that subsystem only.
至于我为什么要这样做,我的任务是移动应用程序的一个子系统以在后台线程中工作,但它仍然只是有时和某些类型的阻塞用户的工作流(主线程)仅与该子系统相关的工作。
采纳答案by CrashCodes
I went with something I haven't seen posted yet which is to use MessageQueues.
我使用了一些我还没有看到发布的东西,那就是使用 MessageQueues。
- The MainThread blocks while waiting for the next message on a queue.
- The background thread posts different types of messages to the MessageQueue.
- Some of the message types signal the MainThread to update UI elements.
- Of course, there is a message to tell the MainThread to stop blocking and waiting for messages.
- MainThread 在等待队列中的下一条消息时阻塞。
- 后台线程将不同类型的消息发布到 MessageQueue。
- 一些消息类型通知 MainThread 更新 UI 元素。
- 当然,还有一条消息告诉MainThread停止阻塞和等待消息。
Seems over the top considering the windows message loop already exists somewhere, but it works.
考虑到 Windows 消息循环已经存在于某处,这似乎有点过头了,但它确实有效。
回答by Steven A. Lowe
structure your app so that the main thread only performs UI updates, and all other work is done on secondary threads via a work queue; then add a waiting-for-godot flag to your main thread and use it to guard the method that adds items to the work queue
构建您的应用程序,以便主线程仅执行 UI 更新,所有其他工作都通过工作队列在辅助线程上完成;然后向主线程添加一个等待 godot 标志,并使用它来保护将项目添加到工作队列的方法
out of curiosity: why do you want to do this?
出于好奇:你为什么要这样做?
回答by Andrew Theken
You want to use the "BackgroundWorker" class, which will take most of this pain out of this for you.. but as mentioned before, you'll also want to structure it so that the main thread is updating the UI and the worker is doing the heavy lifting.
你想使用“ BackgroundWorker”类,它会为你解决大部分的痛苦..但如前所述,你还想构造它,以便主线程更新用户界面,而工作线程是做繁重的工作。
回答by Maghis
It is easyer then you might think.
这比你想象的要容易。
Suggestion: when you need a thread to perform some occasional work, get it from the threadpool, so you will not need strange/error prone recycling code.
建议:当你需要一个线程来执行一些偶尔的工作时,从线程池中获取它,这样你就不需要奇怪/容易出错的回收代码。
When you want something on another thread to update your UI, you just need a reference to the form and to call Form.Invoke passing the UI code you want the main thread to execute; it's a best pactice, in an event, to release the UI thread as soon as possible.
当您希望另一个线程上的某些内容更新您的 UI 时,您只需要引用该表单并调用 Form.Invoke 传递您希望主线程执行的 UI 代码;在某种情况下,最好尽快释放 UI 线程。
Ie:
IE:
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<int>(delegate(int res)
{
// this is the UI thread
// update it!
label1.Text = res.ToString();
}), result);
});
}
Hope this helps you:)
希望这对你有帮助:)
EDIT: I am sorry, I didn't read the "blocking user workflow" part.
编辑:对不起,我没有阅读“阻止用户工作流程”部分。
WindowsForms is not designed to do that, blocking the main thread is BAD (it handles the messages from the OS).
WindowsForms 不是为这样做而设计的,阻塞主线程是不好的(它处理来自操作系统的消息)。
You don't have to block the user workflow via freezing a form (which would then be considered "Not Responding" by windows), the way to block user workflow is by disabling any control you want (with the Invoke method above if from another thread), even the entire form!!
您不必通过冻结表单来阻止用户工作流(这将被 Windows 视为“未响应”),阻止用户工作流的方法是禁用您想要的任何控件(如果来自另一个控件,则使用上面的 Invoke 方法)线程),甚至整个表格!
回答by Will Dean
Common activities which 'block' the main thread are things like opening messages boxes or modal dialog. The main code appears to block at the MessageBox or ShowDialog call.
“阻塞”主线程的常见活动是诸如打开消息框或模式对话框之类的事情。主要代码似乎在 MessageBox 或 ShowDialog 调用处阻塞。
The way those items work (and MessageBox is just a specialized modal dialog) is that they contain their own message pump while they're blocking.
这些项目的工作方式(而 MessageBox 只是一个专门的模式对话框)是它们在阻塞时包含自己的消息泵。
Although it's a nasty hack, you can do something like this in your app by looping calling Application.DoEvents() to keep the user messages pumping while you're waiting for your other task to complete. You need to be careful because all sorts of nasty things might lead from pumping messages like this - for example someone close the form or reenter your current message handler - the modal dialogs avoid this by effectively disabling input from the form which launches them.
尽管这是一个令人讨厌的黑客,但您可以通过循环调用 Application.DoEvents() 在您的应用程序中执行类似的操作,以在您等待其他任务完成时保持用户消息不断涌出。您需要小心,因为各种令人讨厌的事情可能会因发送这样的消息而导致 - 例如有人关闭表单或重新输入您当前的消息处理程序 - 模态对话框通过有效禁用来自启动它们的表单的输入来避免这种情况。
I did mean to say that BackgroundWorker is a better solution, if you can make it fit. I sometimes combine it with a modal 'progress dialog' to give me the background thread / message pumping and the blocking of the UI thread.
我的意思是说 BackgroundWorker 是一个更好的解决方案,如果你可以让它合适的话。我有时将它与模式“进度对话框”结合起来,为我提供后台线程/消息泵和 UI 线程的阻塞。
Edit - to expand on the last bit:
编辑 - 扩展最后一点:
One approach I've used is to have a 'progress form' class, which takes a BackgroundWorker object as a constructor parameter, and contains handlers for the progress and completion events of the background worker which gets passed to it.
我使用的一种方法是使用“进度表单”类,该类将 BackgroundWorker 对象作为构造函数参数,并包含传递给它的后台工作进程的进度和完成事件的处理程序。
The form which wants the work done creates the background worker and hooks up the 'work' event (can't remember what it's called right now), and then creates a progress dialog to which it passes the background worker. It then modally shows the progress dialog, which means it will wait (but pumping messages) until the progress dialog closes.
想要完成工作的表单创建后台工作人员并连接“工作”事件(现在不记得它叫什么),然后创建一个进度对话框,它将后台工作人员传递给它。然后它会以模态方式显示进度对话框,这意味着它将等待(但会发送消息)直到进度对话框关闭。
The progress form is responsible for starting the BackgroundWorker from its OnLoad override, and closes itself when it sees the BackgroundWorker complete. Obviously you can add message text, progress bars, cancel buttons, whatever to the progress form.
进度表单负责从其 OnLoad 覆盖启动 BackgroundWorker,并在看到 BackgroundWorker 完成时关闭自身。显然,您可以向进度表添加消息文本、进度条、取消按钮等。
回答by Will Dean
You should probably restructure your code as others have suggested, but depending on the behavior you're looking for, you might also want to have a look at using a Thread.Join on your background worker thread. Join actually allows the calling thread to process COM and SendMessage events while it waits for the other thread to finish. This seems like it could be dangerous in come cases, but I've actually had a couple scenarios where it was the only way to wait for another thread to finish cleanly.
您可能应该按照其他人的建议重构您的代码,但根据您正在寻找的行为,您可能还想看看在后台工作线程上使用 Thread.Join 。Join 实际上允许调用线程在等待另一个线程完成时处理 COM 和 SendMessage 事件。这似乎在某些情况下可能很危险,但我实际上有几个场景,这是等待另一个线程干净地完成的唯一方法。
Thread..::.Join Method
Blocks the calling thread until a thread terminates, while continuing to perform standard COM and SendMessage pumping.
Thread..::.Join 方法
阻塞调用线程直到线程终止,同时继续执行标准 COM 和 SendMessage 泵送。
(from http://msdn.microsoft.com/en-us/library/95hbf2ta.aspx)
回答by JFV
I agree with the others that are suggesting you use Background Worker. It does the heavy lifting and allows the UI to continue. You can use the Report Progress of Background Worker to initiate times where the Main Form can be set to be disabled while it performs the actions in the background and then re-enable once the 'certain instances' have completed processing.
我同意其他人建议您使用后台工作人员的意见。它完成了繁重的工作并允许 UI 继续。您可以使用后台工作人员的报告进度来启动主窗体在后台执行操作时可以设置为禁用的时间,然后在“某些实例”完成处理后重新启用。
Let me know if this helps! JFV
让我知道这是否有帮助!JFV
回答by Steven Behnke
If you could adjust your code so that you set a flag once a process has begun and then check that in the UI before you start an additional operation I think you'd have a much easier time coding this. I would create a delegate that could be called from the thread in the threadpool or user created thread to update on progress in the UI. Once the background process has been completed switch the flag and now normal UI operations can continue. The only caveat you need to be aware of is that when you update UI components you must do it on the thread they were created on, the main/UI thread. In order to accomplish this you can call the Invoke() method on any control that lives on that thread and pass it the delegate and parameters you need to call it.
如果您可以调整代码,以便在流程开始后设置一个标志,然后在开始其他操作之前在 UI 中检查该标志,我认为您可以更轻松地编写代码。我会创建一个可以从线程池中的线程或用户创建的线程调用的委托,以更新 UI 中的进度。后台进程完成后,切换标志,现在可以继续正常的 UI 操作。您需要注意的唯一警告是,当您更新 UI 组件时,您必须在创建它们的线程(主/UI 线程)上执行此操作。为了实现这一点,您可以在该线程上的任何控件上调用 Invoke() 方法,并将调用它所需的委托和参数传递给它。
Here's a link to a tutorial I wrote some time ago about how to use Control.Invoke():
这是我前段时间写的关于如何使用 Control.Invoke() 的教程的链接:
回答by Stormenet
Just a code snippet: don't have much time sorry :)
只是一个代码片段:没有太多时间抱歉:)
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<string>(ReportBack), new object[] { p });
return;
}
this.Text = p;
}
回答by Stormenet
It is best to dispatch the work but if you must, maybe something like this. Just call this method to wait for the signal rather than calling the waitone.
最好分派工作,但如果必须的话,也许是这样的。只需调用此方法等待信号而不是调用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;
}