应用SynchronizationContext来实现对thread-safe UI的同步访问的2种不同Programming Model

前言:

当有多个threads访问某个thread-safe的资源如UI时,必须有某种mechanism来保证这些thread marshaling。对于需要thread-safe的资源如果没有施加任何措施而被其他thread同时访问的话,会抛出如下类似的invalidoperationException

Cross - thread operation not valid: Control  ' m_CounterLabel '  accessed from a thread other than the thread it was created on.

excpetion screenshot如下:

 

应用SynchronizationContext来实现对thread-safe UI的同步访问的2种不同Programming Model

Programming Model 1:

在下面的sample中,MyForm提供了一个叫做允许client访问来获得该UI的MySynchronizationContext的propertity。MySynchronizationContext是通过在在MyForm的Constructor中通过获得当前线程的上下文来初始化的。 MyForm也提供了Counter Property来更新服务端的Windows Forms label. 当然 .Counter只能被占有Form的当前thread来访问。

source code:

Service UI

using  System;
using  System.Collections.Generic;
using  System.ComponentModel;
using  System.Data;
using  System.Drawing;
using  System.Linq;
using  System.Text;
using  System.Windows.Forms;

namespace  UISynchronizationContext_Service
{
    
public   partial   class  MyForm : Form
    {
        
private  System.Windows.Forms.Label m_CounterLabel;
        System.Threading.SynchronizationContext m_SynchronizationContext;

        
public  MyForm()
        {
            InitializeComponent();
            m_SynchronizationContext 
=  System.Threading.SynchronizationContext.Current;
            System.Diagnostics.Debug.Assert(m_SynchronizationContext 
!=   null );
        }

        
public  System.Threading.SynchronizationContext MySynchronizationContext
        {
            
get
            {
                
return  m_SynchronizationContext;
            }
        }

        
public   int  Counter
        {
            
get  {  return  Convert.ToInt32(m_CounterLabel.Text); }
            
set  { m_CounterLabel.Text  =  value.ToString(); }
        }
        
private   void  MyForm_Load( object  sender, EventArgs e)
        {

        }
    }
}

Service Contract and Implementation

using  System;
using  System.Collections.Generic;
using  System.Linq;
using  System.Text;
using  System.ServiceModel;

namespace  UISynchronizationContext_Service
{
    [ServiceContract]
    
public   interface  IFormManager
    {
        [OperationContract(IsOneWay 
=   true )]
        
void  IncrementLabel();
    }

    
class  MyContract:IFormManager
    {
        
public   void  IncrementLabel()
        {
            MyForm form 
=  System.Windows.Forms.Application.OpenForms[ 0 as  MyForm;
            System.Diagnostics.Debug.Assert(form 
!=   null );
            SendOrPostCallback callback 
=   delegate
            {
                form.Counter
++ ;
            };

            form.MySynchronizationContext.Send(callback, 
null );
            
        }
    }

    
static   class  Program
    {
        
///   <summary>
        
///  The main entry point for the application.
        
///   </summary>
        [STAThread]
        
static   void  Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(
false );

            ServiceHost host 
=   new  ServiceHost( typeof (MyContract));
            host.Open();

            Application.Run(
new  MyForm());
            host.Close();
        }
    }
}

运行结果:

Service Updated UI by multiple threads应用SynchronizationContext来实现对thread-safe UI的同步访问的2种不同Programming Model

#1 Client Calls for service to update service's UI 

应用SynchronizationContext来实现对thread-safe UI的同步访问的2种不同Programming Model 

#2 Client Calls for service to update service's UI 

应用SynchronizationContext来实现对thread-safe UI的同步访问的2种不同Programming Model

缺点:

该programming model的defiency在于:服务(service)和UI form过于耦合。很显然,这是一种不好的设计模式。如果在Form中需要更新多个control的话,会非常不方便。 比较理想的设计方法是将Service和UI form decouple,两者不受相互影响。

Programming Model 2:

那么,如何实现这样的programming model呢?我们可以自定义thread-safe control。将一些与windows form的同步上下文的要求thread-safe的操作封装在这些所谓的safe control里面,从而将其与service 脱耦。

例如,我们对上面代码略作修改:

1. 我们在Form上添加一个m_MyTextBox的引用, 但是其实这个m_MyTextBox是一个MyTexbBox的reference, 其是一个自定义的derived from System.Windows.Forms.Text的safe textbox class。我们将其某些Method或者Propertity进行了Override以便其运行在thread-safe之下:

以下是MyTextBox这个自定义TextBox的definition

using  System;
using  System.Collections.Generic;
using  System.Linq;
using  System.Text;
using  System.Windows.Forms;
using  System.Threading;

namespace  UISynchronizationContext_Service
{
    
class  SafeTextBox:TextBox
    {
        SynchronizationContext m_SynchronizationContext 
=  SynchronizationContext.Current;

        
public   override   string  Text
        {
            
set
            {
                SendOrPostCallback setText 
=   delegate ( object  text)
                {
                    
base .Text  =  text  as   string ;
                };
                m_SynchronizationContext.Send(setText, value);
            }
            
get
            {
                
string  text  =  String.Empty;
                SendOrPostCallback getText 
=   delegate
                {
                    text 
=   base .Text;
                };
                m_SynchronizationContext.Send(getText, 
null );
                
return  text;               
            }
        }

       
    }
}

然后假设在ServiceContract中新增加一个Operation:

[OperationContract(IsOneWay = true)]

void UpdateTextBox(string textBoxValue);

用来实现对TextBox内Text的修改。

其Implementation实现如下:

class  MyContract:IFormManager
    {
        
public   void  IncrementLabel()
        {
            MyForm form 
=  System.Windows.Forms.Application.OpenForms[ 0 as  MyForm;
            System.Diagnostics.Debug.Assert(form 
!=   null );
            SendOrPostCallback callback 
=   delegate
            {
                form.Counter
++ ;
            };

            form.MySynchronizationContext.Send(callback, 
null );
            
        }

       
public   void  UpdateTextBox( string  textBoxValue)
        {
            MyForm form 
=  System.Windows.Forms.Application.OpenForms[ 0 as  MyForm;
            form.TextBoxValue = textBoxValue;
        }
    }

 

就只有一条非常简单的语句!而那些所有对Control的同步上下文操作都被封装在了其safe control中了 :) so cool.

 当然,client代码也作简单修改如下:

using  System;
using  System.Collections.Generic;
using  System.Linq;
using  System.Text;

namespace  UISynchronizationContext_Client
{
    
class  Program
    {
        
static   void  Main( string [] args)
        {
            FormManagerClient proxy 
=   new  FormManagerClient();
            Console.WriteLine(
" Press any key to continue processing " );
            Console.ReadLine();
            proxy.IncrementLabel();
            
for  ( int  i  =   0 ; i  <   10 ; i ++ )
            {
                proxy.UpdateTextBox(
" value set from #1 client is  "   +  i.ToString());
                System.Threading.Thread.Sleep(
2000 );
            }
            Console.WriteLine(
" Updated Service's UI " );
            Console.ReadLine();
        }
    }
}

为了看出Service的safe control是如何marshal这些update control的请求的,特意给出了连续10次这样的操作。

如何有多个不同的client在不断call 这个service,可以看到service端的TextBox中的Text不断的被不同的Client thread Update。

运行结果:

Service UI运行结果如下:

应用SynchronizationContext来实现对thread-safe UI的同步访问的2种不同Programming Model

应用SynchronizationContext来实现对thread-safe UI的同步访问的2种不同Programming Model

 For source code regarding to this article, pls press here to download

你可能感兴趣的:(programming)