【摘自Devx】How to pass parameters to Thread in Windows Forms -- and get results back

多线程参数传递与结果返回
本文介绍如何在Windows Forms应用中启动新线程并安全地传递参数及获取返回结果,包括通过类上下文启动线程、安全访问窗体控件及从线程返回值。
Launching new threads is easy, but it's not as obvious how you can pass parameters to the threads and get the results back. In addition, accessing Windows Forms controls from multiple threads can cause problems. In this article, you'll see how to pass parameters, receive results, and access controls safely from multiple threads in a Windows Forms application.  

by Russell Jones
March 10, 2003

Introduction
3591.gifhe .NET framework makes it easy to start code execution on a new thread—improving user interface design by letting you execute lengthy procedures on one thread while leaving the UI thread active and available to handle user interaction.

Starting a new thread is simple in VB.NET—you add the System.Threading namespace to your module to simplify naming, create a new thread using a delegate to tell it which method to start in, and then call its Start method to begin execution.

For example, suppose you have a button on a form; when the user presses the button, you want to launch a separate thread that performs some task. For this article, a simple counter method suffices to simulate a long-running procedure.

Note: The downloadable code for this article contains both VB.NET and C# versions.


  
ContractedBlock.gif ExpandedBlockStart.gif Count()
None.gifprivate void Count()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    
for (int i = 1; i < 26; i++)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        Console.WriteLine(
i.ToString());
InBlock.gif        Thread.Sleep(
100);
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

None.gif
But rather than showing the user an hourglass cursor and disabling the button while the task executes, you want to let the user continue to interact with the program. Create a new Windows Form with a button named btnLaunchThread and set its Text property to Launch Thread. Next, create a Count() method that counts from 1 to 25, writing the current counter value to the Output window. Because that would normally happen very quickly, the code also uses the Thread.CurrentThread method to get the currently executing thread, and causes it to sleep for 100 milliseconds, which simulates a lengthy process better. Here's the code.


  
ContractedBlock.gif ExpandedBlockStart.gif btnLaunchThread_Click
None.gifprivate void btnLaunchThread_Click(object sender, System.EventArgs e)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    Thread t 
= new Thread(new ThreadStart(this.Count));   
InBlock.gif    t.IsBackground 
= true;
InBlock.gif    t.Start();
ExpandedBlockEnd.gif}

None.gif
Run the code and click the Launch Thread button. You'll see the output window slowly fill with numbers from 1 to 25. Click the button again—in fact, click it several times. What happens? Each time you click the button, the application launches a new thread, which then starts displaying numbers in the output window. To make things a little clearer, you can assign each thread a name and display that as well. The sample Form1.vb file contains the altered code:


  
ContractedBlock.gif ExpandedBlockStart.gif Show Thread Name
None.gifprivate void btnLaunchThread_Click(object sender, System.EventArgs e)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    Thread t 
= new Thread(new ThreadStart(this.Count));
InBlock.gif    threadCount 
+= 1;
InBlock.gif    t.Name 
= "Thread " + threadCount.ToString();
InBlock.gif    t.IsBackground 
= true;
InBlock.gif    t.Start();
ExpandedBlockEnd.gif}

None.gif
private void Count() 
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif
InBlock.gif    
for (int i = 1; i < 26; i++
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        Console.WriteLine(Thread.CurrentThread.Name 
+ "" + i.ToString());
InBlock.gif        Thread.Sleep(
100);
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

None.gif
None.gif
 
After changing the code, when you click the button several times, you'll see each thread's name appear in front of the counter value. That's straightforward enough. Each button click launches an individual thread that counts from 1 to 25. But what if you didn't know how many times to loop? What if you wanted the user to specify a value for the number of counter iterations, and pass that to the new thread?

Here's where the simplicity breaks down a little. The Thread class constructor accepts only a ThreadStart delegate (the delegate that represents the method in which to start the thread), and there's no overloaded Thread.Start() method that accepts parameter values.


Pass Parameters to Threads with a Class
The trick is to start the thread in the context of a class that already has the parameter values you want to send. Rather than launching the new thread directly from the btnLaunchThread_Click event, you create a new class that has properties to hold the ThreadStart delegate and the number of counter iterations.

The CounterArgs class serves that purpose. The public iterations field holds the number of counter iterations, and the startDelegate field holds a StartCounterDelegate, which is a pre-created delegate that matches the Count() method in the form. The class has one method, StartCounting(), which calls the method represented by the StartCounterDelegate (the Count() method) and passes the number of iterations. In other words, to launch a new thread and pass the number of iterations, you create a new CounterArgs class, set its Iterations property, and then create a new StartCounterDelegate which represents the Count() method. Finally, you create a new Thread with a ThreadStart delegate that represents the CounterArgs.StartCounting method. When you start the new thread, it runs in the CounterArgs class, and therefore it has access to the values of the Iterations and StartDelegate fields.


Here's the complete code for the CounterArgs class.


  
ContractedBlock.gif ExpandedBlockStart.gif CounterArgs
None.gifprivate class CounterArgs 
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif
InBlock.gif    
public int Iterations = 0;
InBlock.gif    
public StartCounterDelegate StartDelegate;
InBlock.gif    
public void StartCounting() 
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        StartDelegate(Iterations);
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

None.gif
The remaining code is simple: create a Count method that accepts the number of iterations to count.

   
ContractedBlock.gif ExpandedBlockStart.gif Count(int)
None.gifpublic void Count(int iterations) 
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    
for (int i = 0; i <= iterations; i++
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        Console.WriteLine(Thread.CurrentThread.Name 
+ "" + i.ToString());
InBlock.gif        Thread.Sleep(
100);
ExpandedSubBlockEnd.gif    }

InBlock.gif    Console.WriteLine(
"Ending Thread: " +Thread.CurrentThread.Name);
ExpandedBlockEnd.gif}

None.gif
None.gif
When you click the button on Form 1, the thread starts and writes the expected output—and if you click the button several times, you'll see the correct output for each thread.

  
ContractedBlock.gif ExpandedBlockStart.gif btnLaunchThread_Click_1
None.gifprivate void btnLaunchThread_Click_1(object sender, System.EventArgs e)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    
int iterations = 0;
InBlock.gif    
try 
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        iterations 
= Int32.Parse(this.txtIterations.Text);
ExpandedSubBlockEnd.gif    }

InBlock.gif    
catch 
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        MessageBox.Show(
"Invalid entry. Enter an integer value.");
InBlock.gif        
return;
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    CounterArgs ca 
= new CounterArgs();
InBlock.gif    ca.Iterations 
= iterations;
InBlock.gif    ca.StartDelegate 
= new StartCounterDelegate(this.Count);
InBlock.gif    Thread t 
= new Thread(new ThreadStart(ca.StartCounting));
InBlock.gif    threadCount 
+= 1;
InBlock.gif    t.Name 
= "Thread " + threadCount.ToString();
InBlock.gif    Console.WriteLine(
"Starting thread " + t.Name + " to count " + iterations.ToString() + " times.");
InBlock.gif    t.IsBackground 
= true;
InBlock.gif    t.Start();
ExpandedBlockEnd.gif}

None.gif
None.gif
Accessing Windows Controls from Launched Threads
You might ask: Why not just have the Count() method grab the iteration parameter value from the txtIterations control? That's a good question. The answer is that Windows Forms are not thread-safe—they run on a single thread, and if you try to access the controls on a form from multiple threads, you're bound to have problems. You should only access Windows Forms controls from the thread on which they were created. In this article, I've called that the "main form thread".

To make matters worse, you can access the controls from other threads without causing an exception—despite the fact that you shouldn't. For example, if you write code to grab the txtIteration.Text value, it will usually work. More commonly, you want to write to Windows Forms controls from multiple threads, for example, changing the contents of a TextBox—and because that alters the control data, it's where most control threading problems occur.

The sample Form3.vb file shows you how to access and update control values safely. The form launches the threads in the same way you've already seen, but displays the results in a multi-line TextBox. To access Windows controls safely from a different thread, you should query the InvokeRequired property implemented by the Control class and inherited by all the standard controls. The property returns True if the current thread is not the thread on which the control was created—in other words, a return value of True means you should not change the control directly from the executing thread. Instead, call the control's Invoke() method using a delegate and (optionally) a parameter array of type Object. The control uses the delegate to call the method in the context of its own thread, which solves the multithreading problems. This is the only thread-safe way to update control values.

In Form 3, the button click code is identical to the code in Form 2—it creates a thread and starts it. The Count() method is different; it contains the check for InvokeRequired, and a SetDisplayText() method to handle the control update. The Count() method code looks more complicated than it is.


  
ContractedBlock.gif ExpandedBlockStart.gif Count(int)
None.gifprivate void Count(int iterations ) 
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    Thread t 
= Thread.CurrentThread;
InBlock.gif    
for (int i = 0; i <= iterations; i++
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
if (this.txtDisplay.InvokeRequired) 
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
this.txtDisplay.Invoke(new ChangeTextControlDelegate(this.SetDisplayText),
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
new Object[] dot.gif{t.Name, txtDisplay, i.ToString()});
ExpandedSubBlockEnd.gif        }

InBlock.gif        
else 
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
this.SetDisplayText(t.Name, txtDisplay, i.ToString());
ExpandedSubBlockEnd.gif        }

InBlock.gif        
if (this.txtDisplay.InvokeRequired) 
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            Thread.Sleep(
100);
ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif    }

InBlock.gif    
if (this.txtDisplay.InvokeRequired) 
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
this.txtDisplay.Invoke(new ChangeTextControlDelegate(this.SetDisplayText),
ExpandedSubBlockStart.gifContractedSubBlock.gif                
new Object[] dot.gif{t.Name, txtDisplay, "Ending Thread"});
ExpandedSubBlockEnd.gif    }

InBlock.gif    
else 
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
this.SetDisplayText(t.Name, txtDisplay, "Ending Thread");
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

None.gif
None.gif
Within the loop, the application checks to see if the current thread is capable of directly altering the txtDisplay TextBox contents, by checking the value of InvokeRequired. When the value returns True the code uses the Control.Invoke method to call the SetDisplayText() method via a ChangeTextControl delegate:


  
None.gif private   delegate   void  ChangeTextControlDelegate(
            string  aThreadName,
            TextBox aTextBox,
            
string  newText);
None.gif
The SetDisplayText() method appends the name of the thread and the newText parameter to the specified TextBox. Because TextBoxes have a maximum length, the SetDisplayText() method checks to ensure that the TextBox is not "full" of text; if it is, it removes all but the last 1,000 characters before appending the new text. If the TextBox is not full, the code simply appends the new text.


  
ContractedBlock.gif ExpandedBlockStart.gif SetDisplayText
None.gifpublic void SetDisplayText(string aThreadName, TextBox aTextBox, String newText) 
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    
if (aTextBox.Text.Length + newText.Length > aTextBox.MaxLength) 
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        aTextBox.Text 
= aTextBox.Text.Substring(aTextBox.Text.Length - 1000);
ExpandedSubBlockEnd.gif    }

InBlock.gif    aTextBox.AppendText(aThreadName 
+ "" + newText + System.Environment.NewLine);
ExpandedBlockEnd.gif}

None.gif
None.gif

Why Not Always Use Control.Invoke?
Strictly speaking, you don't need to check the InvokeRequired property for this particular application, because here only new threads call the Count() method, so InvokeRequired always returns True, and the Else portion of the two If structures never fires. In fact, if you're willing to take a miniscule performance hit, you can simply eliminate the check and always use the Invoke method. Calling Invoke from the main form thread doesn't raise an exception. You should try it yourself, to see what happens.

Here's a simplified version of Count that always uses Invoke.

  
ContractedBlock.gif ExpandedBlockStart.gif Count - always Invoke
None.gifpublic void Count(int iterations)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    
int i;
InBlock.gif    
for (i=0;i<iterations;++i)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
this.txtDisplay.Invoke(dot.gif);
InBlock.gif        Thread.CurrentThread.Sleep(
100);
ExpandedSubBlockEnd.gif    }

InBlock.gif    
this.txtDisplay.Invoke(dot.gif);
ExpandedBlockEnd.gif}

None.gif
To test it, add this line to the end of the btnStartThread_Click event code to call the Count() method on the main form thread.

  
None.gif this .Count(i);

Unfortunately, you'll find that this simpler version does freeze the interface until the main form thread loop finishes, which is exactly why you want to use a separate thread to begin with. If you leave the call to Count() in place at the end of the btnStartThread_Click event but run it against the previous version of the Count() method instead, the interface is "live," but the button won't depress while the main form thread counter is running (because the thread is "asleep" most of the time inside the loop). Interestingly though, the button does still accumulate clicks—the form sticks the click events in the message queue until the current main-thread loop exits. If you continue to click the button the application dutifully launches a new thread for each click—even though you won't see the button depress.

【Commented by Peter-Yung】
这里的trick在于: Thread.Sleep(100); 在我们的示例中,
异步线程把UI的操作委托给UI的创建线程执行的同时,
异步线程每执行一步操作(这里是打印一行信息),就要睡眠100ms,
如果去除InvokeRequired的检查,使得UI的创建线程在执行
this.Count(i)的时候也进入睡眠,从而使得界面“假死”,
按钮呈僵死状态,由于睡眠时间很短,使得消息并不会丢失。
【Personal view, viewer discression is advised!】


Returning Values from Launched Threads
You've seen how to pass parameters to a launched thread; but what if you want to return the results—for example, suppose the thread calculates a total value and you need to know that value in the main thread? To do that you'll need to know when the thread exits and you'll need to be able to access the total value.

The sample Form4.vb contains the code to retrieve the total value. First, alter the Count() method so it creates a total value and returns the total when the loop completes. You need to alter the StartCountingDelegate to match the new signature as well. The boldface lines in the following code show the changes.

 
ContractedBlock.gif ExpandedBlockStart.gif int Count(int)
None.gifdelegate int StartCounterDelegate(int iterations);
None.gif
private int Count(int iterations)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    Thread t 
= Thread.CurrentThread;
InBlock.gif    
int total = 0;
InBlock.gif    
for (int i = 0; i <= iterations; i++)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        total 
+= i;
InBlock.gif        
if (this.txtDisplay.InvokeRequired)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
this.txtDisplay.Invoke(new ChangeTextControlDelegate(this.SetDisplayText),
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
new Object[] dot.gif{ t.Name, txtDisplay, i.ToString() });
ExpandedSubBlockEnd.gif        }

InBlock.gif        
else
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
this.SetDisplayText(t.Name, txtDisplay, i.ToString());
ExpandedSubBlockEnd.gif        }

InBlock.gif        
if (this.txtDisplay.InvokeRequired)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            Thread.Sleep(
100);
ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif    }

InBlock.gif    
if (this.txtDisplay.InvokeRequired)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
this.txtDisplay.Invoke(new ChangeTextControlDelegate(this.SetDisplayText),
ExpandedSubBlockStart.gifContractedSubBlock.gif                
new Object[] dot.gif{ t.Name, txtDisplay, "Ending Thread" });
ExpandedSubBlockEnd.gif    }

InBlock.gif    
else
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
this.SetDisplayText(t.Name, txtDisplay, "Ending Thread");
ExpandedSubBlockEnd.gif    }

InBlock.gif    
return total;
ExpandedBlockEnd.gif}

None.gif
None.gif

The thread launches in the context of the CounterArgs class, so you can get the return value there, but more typically, you'd want the CounterArgs class to notify the main form thread when the count is done and the results are available. The answer is to create an event and raise it in the CounterArgs class when the Count() method returns. The DoneCounting event passes the current thread name and the total to the event handler. Although you can pass anything you want as event arguments, the .NET convention is to pass the object invoking the event as the first argument and pass an EventArgs, or a class derived from EventArgs as the second argument. The DoneCountingEventArgs class inherits from EventArgs and contains the data you need to send.


 
ContractedBlock.gif ExpandedBlockStart.gif DoneCountingEventArgs
None.gifpublic class DoneCountingEventArgs : EventArgs
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    
private string m_threadName = null;
InBlock.gif    
private int m_total = 0;
InBlock.gif    
public DoneCountingEventArgs(string threadName, int total)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        m_threadName 
= threadName;
InBlock.gif        m_total 
= total;
ExpandedSubBlockEnd.gif    }

InBlock.gif    
public string ThreadName
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
get
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
return m_threadName;
ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif    }

InBlock.gif    
public int Total
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
get
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
return m_total;
ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

None.gif
None.gif
Here are the alterations you need to make to the CounterArgs class.

 
ContractedBlock.gif ExpandedBlockStart.gif CounterArgs
None.gifprivate class CounterArgs
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    
public event DoneCountingDelegate Done;
InBlock.gif    
public int Iterations;
InBlock.gif    
public StartCounterDelegate StartDelegate;
InBlock.gif    
public void StartCounting()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
int total;
InBlock.gif        total 
= StartDelegate(Iterations);
InBlock.gif        DoneCountingEventArgs args 
= new DoneCountingEventArgs(Thread.CurrentThread.Name, total);
InBlock.gif        
// Modified by Peter-Yung
InBlock.gif
        if (Done != null)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            Done(
this, args);
ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

None.gif
None.gif
Next you need an event-handler in the main form to handle the DoneCounting event and display the total:


 
ContractedBlock.gif ExpandedBlockStart.gif this.Done
None.gif// Modified by Peter-Yung: I added a Label2 onto Form4 to show the result back from Thread
None.gif
delegate void SetResultLabelDelegate(string txt);
None.gif
private void SetResultLabel(string txt)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    
this.label2.Text = txt;
ExpandedBlockEnd.gif}

None.gif
public void Done(object sender, DoneCountingEventArgs e)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif    
/**//*if (txtDisplay.InvokeRequired) 
InBlock.gif      {
InBlock.gif      Console.Write("Done is executing on the launched thread.");
InBlock.gif      }
InBlock.gif      else 
InBlock.gif      {
InBlock.gif      Console.Write("Done is executing on the maing thread.");
ExpandedSubBlockEnd.gif      }
*/

InBlock.gif    
// Modified by Peter-Yung
InBlock.gif
    Console.WriteLine(e.ThreadName + ": Total=" + e.Total.ToString());
InBlock.gif    
// this.Text = e.ThreadName + ": Total=" + e.Total.ToString();
InBlock.gif
    string txt = e.ThreadName + ": Total=" + e.Total.ToString();
InBlock.gif    
if (this.label2.InvokeRequired)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        SetResultLabelDelegate d 
= new SetResultLabelDelegate(this.SetResultLabel);
InBlock.gif        
this.label2.Invoke(d, txt);
ExpandedSubBlockEnd.gif    }

InBlock.gif    
else
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
this.label2.Text = txt;
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

None.gif
None.gif

 
Then, wire the event to the new handler in the btnLaunchThread_Click code:


 
ContractedBlock.gif ExpandedBlockStart.gif Subscribe
None.gifCounterArgs ca = new CounterArgs();
None.gif
// dot.gif
None.gif
ca.Done += new DoneCountingDelegate(this.Done);
None.gif
That's it. When you run Form 4, you'll see the thread name and total appear in the Output window.

【Commented by Peter-Yung】
千万小心,请仔细阅读作者下面的这段话
【Personal view, viewer discression is advised!】


One final note: The Done() method is defined in the main form, but the CounterArgs class raises the DoneCounting event from the launched thread. When that event fires, it causes the Done() method to execute. Which thread do you think the Done() method executes on, the main form thread, or the launched thread? Add this code to the Done() method to find out.


 
ContractedBlock.gif ExpandedBlockStart.gif
None.gifif (txtDisplay.InvokeRequired)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    Console.WriteLine(
"Done is executing on the Launched thread.");
ExpandedBlockEnd.gif}

None.gif
else
ExpandedBlockStart.gifContractedBlock.gif
dot.gif{
InBlock.gif    Console.WriteLine(
"Done is executing on the main form thread.");
ExpandedBlockEnd.gif}

None.gif
When you add the preceding code and run Form 4, you'll see that the event-handler runs in the context of the thread that raised the event. Therefore, you have to be careful how you access controls in event-handlers that might be fired by launched threads

Another way to perform the same task is to create an EndCounterDelegate with the same signature as the Done() method. Add a public field of type EndCounterDelegate to the CounterArgs class, set the delegate's method to the Done() method, and call it when the Count() method completes. As both the concept and the code is nearly identical to the event-raising example in Form 4, I won't show it here, but you can find it in the downloadable sample code in Form 5. Note that you must check IsInvokeRequired using this technique as well.
【Commented by Peter-Yung】
作者在上面阐述的一种技术是多余的,
你不如直接使用BeginInvoke和EndInvoke替代之,
还省去了管理线程的麻烦
【Personal view, viewer discression is advised!】

To sum up, to pass parameters to threads, you need to launch them in the context of a class that has access to the parameter values. To return values created during the execution of a launched thread, raise events or use a delegate to pass the values to a specific method when the thread exits. If you need to access controls created on the main form thread during thread execution, always use the InvokeRequired property to check whether you need to use the control's Invoke() method to access its data.

转载于:https://www.cnblogs.com/Peter-Yung/archive/2007/02/19/652505.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值