none
双向调用中 服务执行回调方法的问题。 RRS feed

  • 常规讨论

  • 1.今天看了篇blog 按照提供的例子做了个测试,代码如下
    服务端

    [ServiceContract(CallbackContract = typeof(ICalculateCallback))]
    
        public interface ICalculate
    
        {
    
            [OperationContract]
    
            void Add(double op1, double op2);
    
        }
    
    
    
        public interface ICalculateCallback
    
        {
    
            [OperationContract]
    
            void DisplayResult(double result);
    
        }
    
    
        [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)]
    
        public class CalculateService1 : ICalculate
    
        {
    
            public static ListBox DisplayPanel
    private static SynchronizationContext synchContext; public static SynchronizationContext SynchContext { get { return synchContext; } set { synchContext = value; } }

    #region ICalculate Members
    delegate void callbackFun(double d);
    public void Add(double op1, double op2) { Helper.Log_Write("CalculateService1 Add"); double result = op1 + op2; ICalculateCallback callback = OperationContext.Current.GetCallbackChannel<ICalculateCallback>(); //更新服务端的窗口中的 ListBox DisplayPanel.Items.Add(string.Format("{0} + {1} = {2}", op1, op2, result)); //执行客户端回调 callback.DisplayResult(result); //callbackFun cf = new callbackFun(callback.DisplayResult); //cf.BeginInvoke(result, null, null); } #endregion }<br/>
    宿主
    宿主端界面为 一个ListBox用来显示 两数相加的情况。
        public partial class FormService1 : Form
    
        {
    
            ServiceHost host = null;
    
            public FormService1()
    
            {
    
                InitializeComponent();
    
            }
    
            private void FormService_Load(object sender, EventArgs e)
    
            {
    
                //将线程信息写入文件
    
                  Helper.Log_Write("FormService1 FormService_Load");
    
                host = new ServiceHost(typeof(CalculateService1));
    
                CalculateService1.DisplayPanel = this.ListResult;
    
                CalculateService1.SynchContext = SynchronizationContext.Current;
    
                host.Opened += delegate { this.Text += "The calculate service has been started up!"; };
    
                host.Open();
    
            }
    
        }
    
    
    
    
    服务端 App.config
    <service name="Service.CalculateService" behaviorConfiguration="CalculateServiceBehavior">
    
        <endpoint address="calculateservice" binding="netTcpBinding" bindingConfiguration="" contract="IContract.ICalculate" />
    
        <endpoint address="mexTcpAdd" binding="mexTcpBinding" contract="IMetadataExchange" />
    
        <host>
    
            <baseAddresses>
    
                <add baseAddress="net.tcp://localhost:8888/" />
    
            </baseAddresses>
    
        </host>
    
    </service>



    客户端
    客户端为 有三个文本框txtNum1,txtNum2,txtResult 和两个按钮 btn1,btn2,
    点击btn1使用同步方法调用服务,并将 txtNum1,txtNum2 传给服务方法,将计算返回值传入要回调方法,回调方法将计算值显示在 客户端的 txtResult中。
    点击btn2使用异步调用 并将 txtNum1,txtNum2 传给服务方法,将计算返回值传入要回调方法,回调方法将计算值显示在 客户端的 txtResult中。

        public partial class FormClient1 : Form
    
        {
    
            public FormClient1()
    
            {
    
                InitializeComponent();
    
            }
    
            private void btnAdd_Click(object sender, EventArgs e)
    
            {
    
                Helper.Log_Write("FormClient1  btnAdd_Click");
    
                Helper.Log_Write("FormClient1  btnAddUseClient_Click");
    
                CalculateCallback1 clculateCallback = new CalculateCallback1();
    
                CalculateCallback1.ResultPanel = this.txtResult;
    
                clculateCallback.SynchContext = SynchronizationContext.Current;
    
                InstanceContext context = new InstanceContext(clculateCallback);
    
                CalculateClient client = new CalculateClient(context);
    
                client.Add(double.Parse(this.txtNum1.Text), double.Parse(this.txtNum2.Text));
    
                Console.WriteLine("btnAdd_Click -------------------------------------------------");
    }
    delegate void AddFun(double d1, double d2);
    private void btnAddUseClient_Click(object sender, EventArgs e) { Helper.Log_Write("FormClient1 btnAddUseClient_Click"); CalculateCallback1 clculateCallback = new CalculateCallback1(); CalculateCallback1.ResultPanel = this.txtResult; clculateCallback.SynchContext = SynchronizationContext.Current; InstanceContext context = new InstanceContext(clculateCallback); CalculateClient client = new CalculateClient(context); //client.Add(double.Parse(this.txtNum1.Text), double.Parse(this.txtNum2.Text)); AddFun addFun = client.Add; AsyncCallback ac = new AsyncCallback(asyncCallbackFun); addFun.BeginInvoke(double.Parse(this.txtNum1.Text), double.Parse(this.txtNum2.Text), ac, addFun); Console.WriteLine("btnAddUseClient_Click -------------------------------------------------"); }
    public static void asyncCallbackFun(IAsyncResult art){ AddFun addFunCallback = art.AsyncState as AddFun; addFunCallback.EndInvoke(art); Helper.Log_Write("addFun.BeginInvoke's AsyncCallback"); } private void FormClient1_Load(object sender, EventArgs e) { } }

    客户端导出元数据
        [System.Diagnostics.DebuggerStepThroughAttribute()]
    
        [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
    
        public partial class CalculateClient : System.ServiceModel.DuplexClientBase<UISynchServiceCallback_FormClient.NS2.ICalculate>, UISynchServiceCallback_FormClient.NS2.ICalculate {
    
    
    
            public CalculateClient(System.ServiceModel.InstanceContext callbackInstance) : 
    
                    base(callbackInstance) {
    
            }
    
            public CalculateClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName) : 
    
                    base(callbackInstance, endpointConfigurationName) {
    
            }
    
            public CalculateClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName, string remoteAddress) : 
    
                    base(callbackInstance, endpointConfigurationName, remoteAddress) {
    
            }
    
            public CalculateClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) : 
    
                    base(callbackInstance, endpointConfigurationName, remoteAddress) {
    
            }
    
            public CalculateClient(System.ServiceModel.InstanceContext callbackInstance, System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : 
    
                    base(callbackInstance, binding, remoteAddress) {
    
            }
    
            public void Add(double op1, double op2) {
    //将代理执行Add方法的线程ID记录进文件 Helper.Log_Write("CalculateClient Add"); base.Channel.Add(op1, op2); } }


    在客户端如果使用btnAdd_Click中的 client.Add(double.Parse(this.txtNum1.Text), double.Parse(this.txtNum2.Text)); 同步方式调用服务方法,
    则当服务执行到 callback.DisplayResult(result); 出现死锁。 是用异步调用则能正常执行。
    看到网上有人说 这里之所以出现死锁的原因如下:

    由于我们对service的调用的是在UI 线程调用的,所以在开始调用到最终得到结果,这个UI Thread会被锁住;但是当service进行了相应的运算的到运算的结果后,
    需要调用callback对象对client进行回调,默认的情况下,Callback的执行是在UI线程执行的。当Callback试图执行的时候,发现UI 线程被锁,只能等待。
    这样形成一个死锁,UI线程需要等待CalculateService执行返回后才能解锁,而CalculateService需要Callback执行完成;而Callback需要等到UI线程解锁才能执行。

    1.不知这个说法是否正确, 如果正确那么 上面所说 “Service调用callback对象对client进行回调,默认的情况下,Callback的执行是在UI线程执行的”,
    这里所说的callback的执行在UI线程执行中的 UI 线程 指的是 服务端UI线程还是 客户端UI线程呢?
    我知道可以通过 将服务端回调方法改为异步,或者将客户端调用改为异步来避免以上问题,但是就是不知道两边都同步的时候导致死锁的时候的具体原因是什么,希望能帮我解答一下。

    另外我测试了下 当客户端使用异步调用时候的线程情况如下。

    线程ID=1            FormService_Load          进入服务端Form的Load方法,此方法构造ServiceHost 并 Open
    线程ID=10          btnAddUseClient_Click    进入btn2的click事件 此线程ID即 UI线程的ID  接下来 客户端的异步调用服务的 Add 方法(在客户端其实是异步调用服务代理的 Add方法)
    线程ID=6            CalculateClient  Add       客户端 服务代理的Add方法被异步调用
    线程ID=1            CalculateService1 Add    进入服务端的Add方法 由于服务由服务端UI线程调用所以 和 FormService_Load  中线程ID相同
    线程ID=11          CalculateCallback1 DisplayResult    进入客户端的回调方法
    线程ID=10          CalculateCallback1 SynchContext.Send    使用客户端的 UI 线程同步上下文的 Synchronization的Send方法执行 对UI的textbox的赋值。
    线程ID=6            addFun.BeginInvoke's AsyncCallback       客户端服务代理的Add方法的异步调用结束。

    2.从上面的执行情况线程ID分析,当客户端异步调用服务方法的时候 由于是异步调用所以 新建了一个 ID = 6的线程用于执行对服务方法的调用, 记下来服务方法
    通过 调用服务Add方法的客户端的信道ICalculateCallback callback = OperationContext.Current.GetCallbackChannel<ICalculateCallback>(); 
    的 callback 对象 执行 callback.DisplayResult(result);
    此时在客户端执行 DisplayResult 方法的线程 ID 为 11,不同于客户端UI的线程ID 10. 而上面所说的 callback对象对client进行回调,默认的情况下,
    Callback的执行是在UI线程执行的 ,
    对于异步调用的时候发现 回调执行的线程并不是 UI线程。

    这个究竟是什么原因。哪位大虾能帮忙解答下 服务调用客户端的回调方法的时候的 线程 ID=11的线程是如何创建出来的呢?


    3.不过另外发现一个问题 就是 在客户端 并发访问服务的时候,服务采用同步方式调用客户端回调方法。
    如果是在调用服务方法前 先打开 宿主 host.Open(),则服务在回调的时候,客户端是在客户端的UI线程上执行回调方法。
    如果是使用在客户端调用服务方法的时候 依赖于代理的自动打开特性,则 服务调用客户端回调的时候 客户端回调则会在新创建的工作线程中执行。


    不知道上面两种方式 在回调时候两种不同线程执行回调方法的情况 原理是怎样的?


    2009年8月27日 14:34

全部回复

  • 请看此论坛的这个讨论;
    http://social.msdn.microsoft.com/Forums/en-US/wcfzhchs/thread/a686462f-a342-4999-a881-31d3db2ad7f2
    Frank Xu Lei--谦卑若愚,好学若饥
    专注于.NET平台下分布式应用系统开发和企业应用系统集成
    Focus on Distributed Applications Development and EAI based on .NET
    欢迎访问老徐的中文技术博客:Welcome to My Chinese Technical Blog
    欢迎访问微软WCF中文技术论坛:Welcome to Microsoft Chinese WCF Forum
    欢迎访问微软WCF英文技术论坛:Welcome to Microsoft English WCF Forum
    2009年8月31日 8:25
    版主
  • 你好,

    1. 如果是同步调用的话, Callback会在客户端的UI线程上调用,因而引起死锁.
    2. 你现在的异步调用实际上类似开了两个线程去调用同步的DoWork方法.这样的话客户端第二次调用要等到第一次完成才能发送.要实现真正的异步调用请使用proxy自动生成的异步调用方法和事件.
    3. 不太清楚你说的情况.如果要控制回调方法的线程,有很多方法,也可以自己来控制线程的SynchronizationContext.我把你在前一个帖里的代码改了一点,你可以看看SynchronizationContext 的作用.

           delegate void DoWorkDelegate();
            static ReentServiceCallback reentrantServiceCallback = new ReentServiceCallback();
            static InstanceContext instanceContext = new InstanceContext(reentrantServiceCallback);
            static ReentrantServiceClient client = new ReentrantServiceClient(instanceContext);
            static SynchronizationContext context = new SynchronizationContext();
            static void ThreadJob()
            {
               //比较注释或使用此行的结果 SynchronizationContext.SetSynchronizationContext(context);
                Console.WriteLine("before call doWorkDelegate.BeginInvoke(null, null); 1");
            
                client.DoWork1();
                Console.WriteLine("after call doWorkDelegate.BeginInvoke(null, null); 1");

            }
            static void Main(string[] args)
     {
                Console.WriteLine("Now In Mian Thread id={0}", System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());

                Console.WindowHeight = 60;
                Console.WindowWidth = 150;

                #region ReentService Demo          
                ThreadStart job = new ThreadStart(ThreadJob);
                Thread thread = new Thread(job);
                thread.Start();

                ThreadStart job2 = new ThreadStart(ThreadJob);
                Thread thread2 = new Thread(job2);
                thread2.Start();

                Console.ReadLine();
    }


    Please remember to mark the replies as answers if they help and unmark them if they provide no help. Need a sample of a technique of Microsoft? Just check out CodeFx first! http://cfx.codeplex.com/
    2009年8月31日 9:13
    版主