一、从基于Windows Application客户端的WCF回调失败谈起
1、Contract:ICalculate & ICalculateCallback
1: namespace Artech.ThreadAffinity.Contracts
3: [ServiceContract(CallbackContract = typeof(ICalculateCallback))]
4: public interface ICalculate
7: void Add(double op1, double op2);
这是Service Contract,下面是Callback Contract,用于显示运算结果:
1: namespace Artech.ThreadAffinity.Contracts
3: public interface ICalculateCallback
6: void DisplayResult(double result);
1: namespace Artech.ThreadAffinity.Services
3: [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)]
4: public class CalculateService:ICalculate
6: public static ListBox DisplayPanel
11: public void Add(double op1, double op2)
13: double result = op1 + op2;
14: ICalculateCallback callback = OperationContext.Current.GetCallbackChannel<ICalculateCallback>();
16: DisplayPanel.Items.Add(string.Format("{0} + {1} = {2}", op1, op2, result));
18: callback.DisplayResult(result);
1: private void HostForm_Load(object sender, EventArgs e)
3: this._serviceHost = new ServiceHost(typeof(CalculateService));
4: CalculateService.DisplayPanel = this.listBoxResult;
5: CalculateService.SynchronizationContext = SynchronizationContext.Current;
6: this._serviceHost.Opened += delegate
8: this.Text = "The calculate service has been started up!";
我们注意到了CalculateService使用到的用于显示所有预算结果的ListBox就是在这了通过static property传递的。
4: <service name="Artech.ThreadAffinity.Services.CalculateService">
8: <add baseAddress="net.tcp://127.0.0.1:8888/calculateservice" />
Client的界面很简单:输入两个操作数,点击“=”按钮,将运算结果显示出来。
先来看看client端对callback contract的实现:
3: public class CalculateCallback : ICalculateCallback
5: public static TextBox ResultPanel;
7: #region ICalculateCallback Members
9: public void DisplayResult(double result)
11: ResultPanel.Text = result.ToString();
4: <endpoint address="net.tcp://127.0.0.1:8888/calculateservice"
1: private void buttonCalculate_Click(object sender, EventArgs e)
3: CalculateCallback.ResultPanel = this.textBoxResult;
5: ICalculate calculator = channelFactory.CreateChannel();
6: calculator.Add(double.Parse(this.textBoxOp1.Text), double.Parse(this.textBoxOp2.Text));
1: public void Add(double op1, double op2)
4: ICalculateCallback callback = OperationContext.Current.GetCallbackChannel<ICalculateCallback>();
6: ThreadPool.QueueUserWorkItem(delegate{ callback.DisplayResult(result); }, null);
1: namespace Artech.ThreadAffinity.Contracts
3: [ServiceContract(CallbackContract = typeof(ICalculateCallback))]
4: public interface ICalculate
6: [OperationContract(IsOneWay = true)]
7: void Add(double op1, double op2);
1: public virtual void Post(SendOrPostCallback d, object state)
2: public virtual void Send(SendOrPostCallback d, object state)
public delegate void SendOrPostCallback(object state);
现在我们来实现我们的第二套方案:让Callback的执行不必绑定到UI线程。为此我们只需要加上如何的CallbackBehavior attribute就可以了。
1: namespace Artech.ThreadAffinity.Clients
3: [CallbackBehavior(UseSynchronizationContext = false)]
4: public class CalculateCallback : ICalculateCallback
6: public static TextBox ResultPanel;
8: #region ICalculateCallback Members
10: public void DisplayResult(double result)
12: ResultPanel.Text = result.ToString();
但是现在我们运行我们的程序,将会出现如下的InvalidOperation异常:
1: namespace Artech.ThreadAffinity.Clients
3: [CallbackBehavior(UseSynchronizationContext = false)]
4: public class CalculateCallback : ICalculateCallback
6: public static TextBox ResultPanel;
7: public static SynchronizationContext SynchronizationContext;
9: #region ICalculateCallback Members
11: public void DisplayResult(double result)
13: SynchronizationContext.Post(delegate { ResultPanel.Text = result.ToString(); }, null);
SynchonizationContext在调用service的时候指定:
1: private void buttonCalculate_Click(object sender, EventArgs e)
3: CalculateCallback.ResultPanel = this.textBoxResult;
4: CalculateCallback.SynchronizationContext = SynchronizationContext.Current;
7: ICalculate calculator = channelFactory.CreateChannel();
8: calculator.Add(double.Parse(this.textBoxOp1.Text), double.Parse(this.textBoxOp2.Text));
五、另一种可选方案:通过ISynchronizeInvoke的Invoke/BeginInvoke
1: public interface ISynchronizeInvoke
4: [HostProtection(SecurityAction.LinkDemand, Synchronization=true, ExternalThreading=true)]
5: IAsyncResult BeginInvoke(Delegate method, object[] args);
6: object EndInvoke(IAsyncResult result);
7: object Invoke(Delegate method, object[] args);
10: bool InvokeRequired { get; }
如何我们放弃基于SynchonizationContext的解决方案,我们也可以通过基于ISynchronizeInvoke的方式来解决这个问题。为此我们这样定义CalculateCallback:
1: namespace Artech.ThreadAffinity.Clients
3: [CallbackBehavior(UseSynchronizationContext = false)]
4: public class CalculateCallback : ICalculateCallback
6: public static TextBox ResultPanel;
7: public delegate void DisplayResultDelegate(TextBox resultPanel, double result);
9: #region ICalculateCallback Members
11: public void DisplayResult(double result)
13: DisplayResultDelegate displayResultDelegate = new DisplayResultDelegate(DisplayResult);
14: ResultPanel.BeginInvoke(displayResultDelegate, new object[] { ResultPanel, result });
17: private void DisplayResult(TextBox resultPanel, double result)
19: resultPanel.Text = result.ToString();
按照我们一般的想法,我们的Service面向若干client,肯定是并发的接收client端的请求,以多线程的方式执行service的操作,那么操作中UI 控件的操作肯定会出现错误。
如何我们将CalculateService的UseSynchronizationContext 设为false:
1: namespace Artech.ThreadAffinity.Services
3: [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant,UseSynchronizationContext = false)]
4: public class CalculateService:ICalculate
6: public static ListBox DisplayPanel
11: public void Add(double op1, double op2)
13: double result = op1 + op2;
14: ICalculateCallback callback = OperationContext.Current.GetCallbackChannel<ICalculateCallback>();
16: DisplayPanel.Items.Add(string.Format("{0} + {1} = {2}", op1, op2, result));
18: callback.DisplayResult(result);
有control被不是创建它的线程操作,肯定会抛出一个InvalidOperationException,就像这样:
1: namespace Artech.ThreadAffinity.Services
3: [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant,UseSynchronizationContext = false)]
4: public class CalculateService:ICalculate
6: public static ListBox DisplayPanel
9: public static SynchronizationContext SynchronizationContext
12: #region ICalculate Members
14: public void Add(double op1, double op2)
16: double result = op1 + op2;
17: ICalculateCallback callback = OperationContext.Current.GetCallbackChannel<ICalculateCallback>();
18: SynchronizationContext.Post(delegate
20: DisplayPanel.Items.Add(string.Format("{0} + {1} = {2}", op1, op2, result));
23: callback.DisplayResult(result);
通过static属性定义的SynchonizationContext在host的时候指定:
1: private void HostForm_Load(object sender, EventArgs e)
3: this._serviceHost = new ServiceHost(typeof(CalculateService));
4: CalculateService.DisplayPanel = this.listBoxResult;
5: CalculateService.SynchronizationContext = SynchronizationContext.Current;
6: this._serviceHost.Opened += delegate
8: this.Text = "The calculate service has been started up!";
联系客服