前言:
当有多个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[
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;
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运行结果如下:
For source code regarding to this article, pls press here to download