In C#, wait on the mainthread while continuing to process UI updates? (.NET 2.0 CF)

匿名 (未验证) 提交于 2019-12-03 01:25:01

问题:

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:

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.



回答2:

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



回答3:

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.



回答4:

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?



回答5:

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)



回答6:

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



回答7:

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/



回答8:

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(ReportBack), new object[] { p });             return;         }         this.Text = p;     } 


回答9:

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


回答10:

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.



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!