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

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

2012年08月17日 ⁄ 综合 ⁄ 共 6188字 ⁄ 字号 评论关闭

前言:

当有多个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如下:

 

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[0as 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

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

 

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

缺点:

该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[0as 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[0as 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运行结果如下:

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

抱歉!评论已关闭.