现在的位置: 首页 > 综合 > 正文

Windows Form 线程之间通信

2013年08月24日 ⁄ 综合 ⁄ 共 9033字 ⁄ 字号 评论关闭



1
采用控件的
Invoke
或则BeginInvoke
来修改控件属性

  
Windows

窗体中的控件被绑定到特定的线程,不具备线程安全性。因此,如果从另一个线程调用控件的方法,那么必须使用控件的一个 Invoke
方法来将调用封送到适当的线程。该属性可用于确定是否必须调用 Invoke

方法, 当不知道什么线程拥有控件时这很有用。

如果已经创建控件的句柄,则除了 InvokeRequired

属性以外,控件上还有四个可以从任何线程上安全调用的方法, 

它们是:Invoke
BeginInvoke
EndInvoke
CreateGraphics
。在后台线程上创建控件的句柄之前调用
CreateGraphics

可能会导致非法的跨线程调用。对于所有其他方法调用,当从另一个线程进行调用时,

应使用这些 Invoke
方法之一。

 

如果控件句柄尚不存在,则
InvokeRequired

沿控件的父级链搜索,直到它找到有窗口句柄的控件或窗体为止。 

如果找不到合适的句柄,InvokeRequired

方法将返回 false
。这意味着如果不需要 Invoke
(调用发生在同一线程上),或者如果控件是在另一个线程上创建的但尚未创建控件的句柄, 

InvokeRequired

可以返回 false
。如果尚未创建控件的句柄,您就不能简单地在控件上调用属性、方法或事件。这可能导致在后台线程上创建控件的句柄,从而隔离不带消息泵的线程上的控件并使应用程序不稳定。

 


InvokeRequired

在后台线程上返回 false
时,您也可以通过检查 IsHandleCreated
的值来避免这种情况。如果尚未创建控件句柄,您必须等到控件句柄已创建,才能调用 Invoke
BeginInvoke
。通常,仅当在应用程序主窗体的构造函数中创建了后台线程时(如同在 Application.Run(new MainForm())
中),

 
在已经显示窗体或取消 Application.Run
之前,才会发生这种情况。一种解决方案是等到已经创建了窗体的句柄,然后启动后台线程。通过调用 Handle
属性强制创建句柄,或者等待 Load

 

事件启动后台进程。
 

一种更好的解决方案是使用 SynchronizationContext
返回的
SynchronizationContext

,而不是使用控件进行线程间封送处理。

delegate
void
Hello
(string
name);

       
void
helloWorld(string
name)

       
{

           
this
.button1.Text
= name;

           
MessageBox
.Show("Hello world "
+ name);

       
}

       
private
void
button1_Click(object
sender, EventArgs
e)

 
      
{

           
bool
b = this
.button1.IsHandleCreated;

           
if
(b)

           
{

               
this
.button1.BeginInvoke(new
Hello
(helloWorld), new
string
[] { "majiang"
});

           
}

       
}

二采用SynchronizationContext

SynchronizationContext
类是一个基类,可提供不带同步的自由线程上下文。 此类实现的同步模型的目的是使公共语言运行库内部的异步/
同步操作能够针对不同的异步模型采取正确的行为。此模型还简化了托管应用程序为在不同的同步环境下正常工作而必须遵循的一些要求。同步模型的提供程序可以简而言之就是允许一个线程和另外一个线程进行通讯,SynchronizationContext
在通讯中充当传输者的角色。 

另外这里有个地方需要清楚的,不是每个线程都附加 SynchronizationContext
这个对象,只有UI
线程是一直拥有的。

 
private
void
mToolStripButtonThreads_Click(object
sender, EventArgs
e)

       
{

           
//
let's see the thread id

           
int
id = Thread
.CurrentThread.ManagedThreadId;

           
Trace
.WriteLine("mToolStripButtonThreads_Click thread: "
+ id);

 

 
          
// grab
the sync context associated to this

           
//
thread (the UI thread), and save it in uiContext

           
//
note that this context is set by the UI thread

           
//
during Form creation (outside of your control)

           
//
also note, that not every thread has a sync context attached to it.

           
SynchronizationContext
uiContext = SynchronizationContext
.Current;

 

           
//
create a thread and associate it to the run method

           
Thread
thread = new
Thread
(Run);

 

           
//
start the thread, and pass it the UI context,

           
//
so this thread will be able to update the UI

           
//
from within the thread

           
thread.Start(uiContext);

       
}

       
private
void
Run(object
state)

  
     
{

           
//
lets see the thread id

           
int
id = Thread
.CurrentThread.ManagedThreadId;

           
Trace
.WriteLine("Run thread: "
+ id);

 

           
//
grab the context from the state

           
SynchronizationContext
uiContext = state as
SynchronizationContext
;

 

           
for
(int
i = 0; i < 100; i++)

           
{

               
// normally you would do some code here

               
// to grab items from the database. or some long

               
// computation

               
Thread
.Sleep(10);

 

               
// use the ui context to execute the UpdateUI method,

               
// this insure that the UpdateUI method will run on the UI
thread.

 

               
// 
Send


方法启动一个同步请求以发送消息

               
//Post

方法启动一个异步请求以发送消息。   

 
              
uiContext.Post(UpdateUI, "line "
+ i.ToString());

           
}

       
}

       
///

<summary>

       
///
This method is executed on the main UI thread.

       
///

</summary>

       
private
void
UpdateUI(object
state)

       
{

        
   
int
id = Thread
.CurrentThread.ManagedThreadId;

           
Trace
.WriteLine("UpdateUI thread:"
+ id);

           
string
text = state as
string
;

           

mListBox.Items.Add(text);

       
}


采用BackgroundWorker
控件

   
BackgroundWorker

类允许您在单独的专用线程上运行操作。耗时的操作(如下载和数据库事务)

  

在长时间运行时可能会导致用户界面 (UI)
似乎处于停止响应状态。如果您需要能进行响应的用户界面, 

而且面临与这类操作相关的长时间延迟,则可以使用 BackgroundWorker
类方便地解决问题。若要在后台执行耗时的操作,请创建一个 BackgroundWorker
,侦听那些报告操作进度并在操作完成时发出信号的事件。 

可以通过编程方式创建
BackgroundWorker

,也可以将它从“工具箱”的“组件”选项卡中拖到窗体上。 

如果在 Windows
窗体设计器中创建 BackgroundWorker
,则它会出现在组件栏中,而且它的属性会显示在“属性”窗口中。若要设置后台操作,请为 DoWork
事件添加一个事件处理程序。在此事件处理程序中调用耗时的操作。 

若要启动该操作,请调用
RunWorkerAsync

。若要收到进度更新通知,请对 ProgressChanged
事件进行处理。 

若要在操作完成时收到通知,请对 RunWorkerCompleted
事件进行处理。您必须非常小心,确保在
DoWork

事件处理程序中不操作任何用户界面对象。而应该通过 ProgressChanged
RunWorkerCompleted
事件与用户界面进行通信。

 
private
int
numberToCompute = 0;

       
private
int
highestPercentageReached = 0;

 

       
private
void
startAsyncButton_Click(System.Object
sender,

         
System.EventArgs
e)

       
{

           
//
Reset the text in the result label.

           
resultLabel.Text = String
.Empty;

 

           
//
Disable the UpDown control until

           
//
the asynchronous operation is done.

           
this
.numericUpDown1.Enabled
= false
;

           
//
Disable the Start button until

           
//
the asynchronous operation is done.

           
this
.startAsyncButton.Enabled
= false
;

 

           
//
Enable the Cancel button while

           
//
the asynchronous operation runs.

           
this
.cancelAsyncButton.Enabled
= true
;

 

           
//
Get the value from the UpDown control.

           
numberToCompute = (int
)numericUpDown1.Value;

 

           
//
Reset the variable for percentage tracking.

      
     
highestPercentageReached = 0;

 

           
//
Start the asynchronous operation.

           

backgroundWorker1.RunWorkerAsync(numberToCompute);

       
}

 

       
private
void
cancelAsyncButton_Click(System.Object
sender,

           
System.EventArgs
e)

       
{

           
//
Cancel the asynchronous operation.

           
this
.backgroundWorker1.CancelAsync();

 

           
//
Disable the Cancel button.

           

cancelAsyncButton.Enabled = false
;

       
}

 

       
//
This event handler is where the actual,

       
//
potentially time-consuming work is done.

       
private
void
backgroundWorker1_DoWork(object
sender,

           
DoWorkEventArgs
e)

       
{

           
//
Get the BackgroundWorker that raised this event.

           
BackgroundWorker
worker = sender as
BackgroundWorker
;

 

           
//
Assign the result of the computation

           
//
to the Result property of the DoWorkEventArgs

           
//
object. This is will be available to the

           
//
RunWorkerCompleted eventhandler.

 
          
e.Result = ComputeFibonacci((int
)e.Argument, worker, e);

       
}

 

       
//
This event handler deals with the results of the

       
//
background operation.

       
private
void
backgroundWorker1_RunWorkerCompleted(

           
object
sender, RunWorkerCompletedEventArgs
e)

       
{

           
//
First, handle the case where an exception was thrown.

           
if
(e.Error != null
)

           
{

               
MessageBox
.Show(e.Error.Message);

           
}

           
else
if
(e.Cancelled)

           
{

               
// Next, handle the case where the user canceled

               
// the operation.

               
// Note that due to a race condition in

               
// the DoWork event handler, the Cancelled

               
// flag may not have been set, even though

               
// CancelAsync was called.

               
resultLabel.Text = "Canceled"
;

           
}

           
else

           
{

               
// Finally, handle the case where the operation

            
   
// succeeded.

               
resultLabel.Text =
e.Result.ToString();

           
}

 

           
//
Enable the UpDown control.

           
this
.numericUpDown1.Enabled
= true
;

           
//
Enable the Start button.

           
startAsyncButton.Enabled
= true
;

           
//
Disable the Cancel button.

           

cancelAsyncButton.Enabled = false
;

       
}

 

       
//
This event handler updates the progress bar.

       
private
void
backgroundWorker1_ProgressChanged(object
sender,

           
ProgressChangedEventArgs
e)

       
{

           
this
.progressBar1.Value
= e.ProgressPercentage;

       
}

 

       
//
This is the method that does the actual work. For this

       
//
example, it computes a Fibonacci number and

       
//
reports progress as it does its work.

       
long
ComputeFibonacci(int
n, BackgroundWorker
worker, DoWorkEventArgs
e)

       
{

           
//
The parameter n must be >= 0 and <= 91.

           
//
Fib(n), with n > 91, overflows a long.

           
if
((n < 0) || (n > 91))

    
       
{

               
throw
new
ArgumentException
(

                   
"value must be >= 0 and <= 91"
, "n"
);

           
}

 

           
long
result = 0;

 

           
//
Abort the operation if the user has canceled.

           
//
Note that a call to CancelAsync may have set

           
//
CancellationPending to true just after the

           
//
last invocation of this method exits, so this

           
//
code will not have the opportunity to set the

           
//
DoWorkEventArgs.Cancel flag to true. This means

           
//
that RunWorkerCompletedEventArgs.Cancelled will

           
//
not be set to true in your RunWorkerCompleted

           
//
event handler. This is a race condition.

 

           
if
(worker.CancellationPending)

           
{

    
           
e.Cancel = true
;

           
}

           
else

           
{

               
if
(n < 2)

               
{

                   
result = 1;

               
}

               
else

               
{

                   
result =
ComputeFibonacci(n - 1, worker, e) +

                            

ComputeFibonacci(n - 2, worker, e);

               
}

 

               
// Report progress as a percentage of the total task.

               
int
percentComplete =

                   
(int
)((float
)n / (float
)numberToCompute * 100);

               
if
(percentComplete > highestPercentageReached)

               
{

                   

highestPercentageReached = percentComplete;

                   

worker.ReportProgress(percentComplete);

               
}

          
 
}

 

           
return
result;

       
}

 

抱歉!评论已关闭.