views:

6790

answers:

10

I want to otherwise block code execution on the main thread while still allowing UI changes to be displayed.

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.


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.

+1  A: 

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

out of curiosity: why do you want to do this?

Steven A. Lowe
As far as changing the app structure as you suggested, it's not exactly my app; and that ship has sailed.
CrashCodes
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.
CrashCodes
@CrashCodes: too vague, sorry; 'sometimes' and 'certain types' could mean anything - how are you going to control the when and the what?
Steven A. Lowe
The 'sometimes' and 'certain types' aren't relevant. They'll be controlled by simple if statements to determine if to wait or how long to wait. The point is that I will need to update the UI while blocking the user's progress through the application.
CrashCodes
@CrashCodes: if that's all you want, call Application.DoEvents() periodically while waiting
Steven A. Lowe
+1  A: 

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.

Andrew Theken
A: 

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.

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).

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!!

Maghis
+2  A: 

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.

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.

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.

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.

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.

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.

Will Dean
I don't think I wanna DoEvents() for exactly the reasons you listed. Can you elaborate on the background worker w/ modal progress dialog? So, I could pop a modal form, update it from the bg thread, and even have the bg thread kill it?
CrashCodes
@Will. Came here to post exactly what you're dialog does... I think that is the best solution.
Nicholas Mancuso
A: 

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 Method

Blocks the calling thread until a thread terminates, while continuing to perform standard COM and SendMessage pumping.

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

A: 

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.

Let me know if this helps! JFV

JFV
A: 

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.

Here's a link to a tutorial I wrote some time ago about how to use Control.Invoke():

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

Steven Behnke
A: 

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;
    }
Stormenet
A: 

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.

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;
}
Application.MessageLoop not supported by CF
CrashCodes
DoEvents will allow the user to press buttons, etc. I just want the screen to be able to be updated.
CrashCodes
A: 

I went with something I haven't seen posted yet which is to use 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.

Seems over the top considering the windows message loop already exists somewhere, but it works.

CrashCodes