Proper Threading in Winforms .NET

太过爱你忘了你带给我的痛 2022-08-13 12:44 222阅读 0赞

原文:http://www.codeproject.com/Articles/4201/Proper-Threading-in-Winforms-NET

  • Download source files - 12.8 Kb

Introduction

This is a small sample application that I built, to figure out how to “properly” do background threading in Winforms under .NET.

There is a well known rule that in the Win32 environment, a control is “owned” by the thread that created it; therefore, no other thread should (or can) safely update a control in any manner. All updates should be “posted” to the thread that owns the control. This posting actually takes place in the examples, by using the Windows Message loop - something that has existed back from Win 3.0 (perhaps earlier) days. This Message Loop still exists in .NET and forms the basis of a “Single Threaded Apartment” which VB 6 knows all too well.

All the stuff I had read was confusing, had all the methods for the work in one single class (WinForm) file. This made it difficult to see the “separation” of the form elements and where the work would be done — and how to signal back to the caller in a separate class.

I think this is a littler easier to understand.

Walkthrough

The application’s form is pretty simple:

Sample screenshot

There is a delegate declaration that identifies the callback that the worker class method will utilize, to signal to the form, what information the primary form’s method needs to update, the status of the WorkerClass processing.

minus.gif Collapse | Copy Code

  1. delegate void ShowProgressDelegate ( int totalMessages,
  2. int messagesSoFar, bool statusDone );

Within the form, the click event fires the method below:

minus.gif Collapse | Copy Code

  1. private void button1_Click(object sender, System.EventArgs e)
  2. {
  3. ShowProgressDelegate showProgress = new
  4. ShowProgressDelegate(ShowProgress);
  5. int imsgs = 100;
  6. if ( cbThreadPool.Checked )
  7. {
  8. object obj = new object[] { this, showProgress, imsgs };
  9. WorkerClass wc = new WorkerClass();
  10. bool rc = ThreadPool.QueueUserWorkItem( new WaitCallback
  11. (wc.RunProcess), obj);
  12. EnableButton( ! rc );
  13. }
  14. else
  15. {
  16. //another way.. using straight threads
  17. //WorkerClass wc = new WorkerClass( this, showProgress, imsgs);
  18. WorkerClass wc = new WorkerClass( this,
  19. showProgress, new object[] { imsgs } );
  20. Thread t = new Thread( new ThreadStart(wc.RunProcess));
  21. //make them a daemon - prevent thread callback issues
  22. t.IsBackground = true;
  23. t.Start();
  24. EnableButton ( false );
  25. }
  26. }

The path taken is based upon the status of the CheckBox. The path is either use ThreadPool or a ThreadStartobject. With the ThreadStart object, the only way to get “state” into the WorkerClass is via the constructor. So, I’ve implemented WorkerClass with 2 constructors that take parameters. The first constructor is a strict 3 param constructor. The second takes the same first 2 params, but the 3rd is a parameter array, allowing more flexibility in future implementations. The sample application only uses the 3 param constructor when the check box is not enable.

Regardless, the first 2 params setup the callback target for WorkerClass.

The ShowProgress method takes a simple list of 3 params that are used to update the text box and progress bar…

minus.gif Collapse | Copy Code

  1. private void ShowProgress ( int totalMessages, int messagesSoFar, bool done )
  2. {
  3. textBox1.Text = String.Format( messagesSoFar.ToString() );
  4. progressBar1.Value = messagesSoFar;
  5. if ( done ) EnableButton ( done );
  6. }

The WorkerClass constructor that we’ll look at here is the one that takes the param list on the end:

minus.gif Collapse | Copy Code

  1. public WorkerClass ( ContainerControl sender,
  2. Delegate senderDelegate, params object[] list)
  3. {
  4. m_sender = sender;
  5. m_senderDelegate = senderDelegate;
  6. m_totalMessages = (int) list.GetValue(0);
  7. }

This just sets up internal members to designate the Form and Form‘s method to call when ShowProgress needs to be called.

Now, the actual work is done in LocalRunProcess using internal instance members. The public methodRunProcess provide the entry point for the caller. The current thread is also forced IsBackground (make it a daemon) so if the parent thread exits, all child threads will be aborted too.

LocalRunProcess calls BeginInvoke (Invoke is the synchronous method) on the sender, passing the delegate instance that was passed in by form on WorkerClass construction. This method call initiates a background call to place a message on the message loop for the form.

minus.gif Collapse | Copy Code

  1. private void LocalRunProcess()
  2. {
  3. int i = 0;
  4. for ( ; i < m_totalMessages; i++)
  5. {
  6. Thread.Sleep(50);
  7. m_sender.BeginInvoke( m_senderDelegate,
  8. new object[] { m_totalMessages, i, false } );
  9. }
  10. m_sender.BeginInvoke( m_senderDelegate,
  11. new object[] { m_totalMessages, i, true } );
  12. }

minus.gif Collapse | Copy Code

  1. public void RunProcess()
  2. {
  3. Thread.CurrentThread.IsBackground = true; //make them a daemon
  4. LocalRunProcess();
  5. }

A few good articles on Winforms, and Winforms over the web. You’ll see the confusion in the 1st 2 articles.

  • Safe, Simple Multithreading in Windows Forms - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnforms/html/winforms06112002.asp
  • A Second Look at Windows Forms Multithreading - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnforms/html/winforms08162002.asp
  • Winform over the web - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/dplywithnet.asp

发表评论

表情:
评论列表 (有 0 条评论,222人围观)

还没有评论,来说两句吧...

相关阅读