前言:
当有多个threads访问某个thread-safe的资源如UI时,必须有某种mechanism来保证这些thread marshaling。对于需要thread-safe的资源如果没有施加任何措施而被其他thread同时访问的话,会抛出如下类似的invalidoperationException
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.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.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
#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.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实现如下:
{
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.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