Whenever I encounter with the problem of threading, I always think that this aspect of programming problem should be directly built into the framework, so we shouldn't need to understand things like STA, MTA, cross threading marshaling etc. the System.Threading.SynchronizationContext and System.ComponentModel.AsyncOperationManager are designed just for this very purpose. But this solution is far from optimal, so I dig into it, and with the help of Don Box's Book Essential .NET-Common Language Runtime, I come up with an approach using CLR's method interception mechanism.
using System;
using System.Threading;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Services;
using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Messaging;
namespace ThreadBoundObjectDemo
{
internal class SynchronizationProxy : RealProxy
{
private SynchronizationContext constructionContext;
private readonly MarshalByRefObject target;
private readonly Object syncRoot;
public SynchronizationProxy(MarshalByRefObject target) : base(typeof(ThreadBoundObject))
{
this.target = target;
this.syncRoot = new Object();
}
public override IMessage Invoke(IMessage msg)
{
IMethodCallMessage call = (IMethodCallMessage)msg;
IMessage response = null;
if (call is IConstructionCallMessage)
{
IConstructionCallMessage ctor = (IConstructionCallMessage)msg;
constructionContext = SynchronizationContext.Current;
RealProxy defaultProxy = RemotingServices.GetRealProxy(target);
defaultProxy.InitializeServerObject(ctor);
response = EnterpriseServicesHelper.CreateConstructionReturnMessage(ctor, (MarshalByRefObject)this.GetTransparentProxy());
}
else
{
if (constructionContext == null || constructionContext.GetType() == typeof(SynchronizationContext))
{
lock (this.syncRoot)
{
response = RemotingServices.ExecuteMessage(target, call);
}
}
else if (constructionContext != SynchronizationContext.Current)
{
constructionContext.Send(new SendOrPostCallback(delegate
{
response = RemotingServices.ExecuteMessage(target, call);
}), null);
}
else
{
response = RemotingServices.ExecuteMessage(target, call);
}
}
return response;
}
}
/// <summary>
/// Indicates that all access on an object members(including fields, methods, properties, indexers etc) is thread safe.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class SynchronizedAttribute : ProxyAttribute
{
public override MarshalByRefObject CreateInstance(Type serverType)
{
MarshalByRefObject target = base.CreateInstance(serverType);
SynchronizationProxy rp = new SynchronizationProxy(target);
return (MarshalByRefObject) rp.GetTransparentProxy();
}
}
}
So if you want an object to be thread safe, all you need to do is to have a class subclass ContextBoundObject and apply the SynchronizedAttribute to it:
using System;
using System.Threading;
using System.Windows.Forms;
namespace ThreadBoundObjectDemo
{
public class Program
{
[STAThread]
public static void Main()
{
Button btn = new Button();
btn.Text = "Text set on creation thread";
Form frm = new Form();
frm.Controls.Add(btn);
btn.Dock = DockStyle.Top;
ThreadBoundObject obj = new ThreadBoundObject(btn);
btn.Click += delegate
{
Thread t = new Thread(new ThreadStart(delegate
{
obj.ButtonText = "Text set from another thread";
}));
t.Start();
};
Application.Run(frm);
}
}
[SynchronizedAttribute]
public class ThreadBoundObject : ContextBoundObject
{
Button btn;
public ThreadBoundObject(Button btn)
{
this.btn = btn;
}
public String ButtonText
{
get { return this.btn.Text; }
set { this.btn.Text = value; }
}
}
}
To be honest, I don't like the design of ContextBoundObject, but suffice it to say, with the power of CLR's method interception mechanism, you can write AOP styled programme much easier, It looks like that you need to write more plumbing code to implement the AOP components, but you would wind up getting the benefit of easy usage pattern.
0 comments:
Post a Comment