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

时间:2008-12-11 20:37:08

标签: c# multithreading

我想在主线程上阻止代码执行,同时仍然允许显示UI更改。

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

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

    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,那么app会在Invoke()调用上死锁。


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

10 个答案:

答案 0 :(得分:2)

你想使用“BackgroundWorker”类,这将为你带来大部分的痛苦......但如前所述,你还需要构造它以便主线程是更新用户界面和工作人员正在进行繁重的工作。

答案 1 :(得分:2)

你可能会认为这比较容易。

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

如果你想在另一个线程上更新你的UI,你只需要对表单的引用,并调用Form.Invoke传递你希望主线程执行的UI代码;在某种情况下,尽快发布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<int>(delegate(int res)
        {
            // this is the UI thread
            // update it!
            label1.Text = res.ToString();
        }), result);
    });
}

希望这可以帮助你:)

编辑:很抱歉,我没有阅读“阻止用户工作流程”部分。

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

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

答案 2 :(得分:2)

“阻止”主线程的常见活动包括打开消息框或模态对话框。主代码似乎在MessageBox或ShowDialog调用时阻塞。

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

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

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

编辑 - 扩展最后一位:

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

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

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

答案 3 :(得分:1)

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

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

答案 4 :(得分:0)

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

    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;
    }

答案 5 :(得分:0)

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

  

Thread .. ::。Join Method

     

阻止调用线程直到a   线程终止,同时继续   执行标准COM和SendMessage   泵送。

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

答案 6 :(得分:0)

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

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

答案 7 :(得分:0)

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

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

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

答案 8 :(得分:0)

我选择了尚未发布的内容,即使用MessageQueues。

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

考虑到Windows消息循环已存在于某处,似乎超过了顶部,但它确实有效。

答案 9 :(得分:0)

最好派遣工作,但如果你必须,可能是这样的。只需调用此方法等待信号而不是调用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;
}
寻找你感兴趣的贴纸↓↓↓
豫ICP备18024241号-1