使用异步委托解决Windows Application应用Duplex Service时出现的Deadlock问题

Artech我的WCF之旅(6)文章中向我们介绍了Windows Application在使用Duplex Service的时候,会出现Deadlock。并且Artech还想我们介绍了两种解决办法:使用IsOneWay修饰Service Contract Operator;在Client端使用新的线程调用Service的Method。
本文继续考虑这个问题的解决方法,在Service和Client端使用异步委托。

我们模拟一个Service来完成字符串反转的操作。
Contract如下定义。
    [ServiceContract(CallbackContract = typeof(IStringBuilderCallBack))]
    
public interface IStringBuilder
    
{
        [OperationContract(IsOneWay 
= false)]
        
void Reverse(string str);
    }


    [ServiceContract]
    
public interface IStringBuilderCallBack
    
{
        [OperationContract(IsOneWay 
= false)]
        
void ShowReerseResult(string src, string dest);
    }
Service实现这个Contract。
    [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
    
public class StringBuilderService : IStringBuilder
    
{
        
IStringBuilder Members

    }

 Client的调用。Client Form直接实现了Callback接口。

    public partial class frmClient : Form, IStringBuilderCallback
    
{
        
private IStringBuilder m_StringBuilderProxy = null;

        
public frmClient()
        
{
            InitializeComponent();

            DuplexChannelFactory
<IStringBuilder> stringbuilderFactory =
                
new DuplexChannelFactory<IStringBuilder>(new InstanceContext(this), "defualtEndpoint");
            m_StringBuilderProxy 
= stringbuilderFactory.CreateChannel();

            ShowInfo(
"The channel is created.");
        }


        
private void btnReserve_Click(object sender, EventArgs e)
        
{
            
try
            
{
                m_StringBuilderProxy.Reverse(textBox4.Text);

                ShowInfo(
"Reserve method from StringBuilder was invoked successful.");
            }

            
catch (Exception ex)
            
{
                ShowError(ex);
            }

        }


        
IStringBuilderCallback Members
    }

这样的结果就是Deadlock。
通过Debug我们发现,实际上死锁的语句是Service上面的
callback.ShowReerseResult(str, dest);
这一行。那么我们就把这个调用变成异步委托调用,释放掉当前Thread的资源,解决死锁。
我们在StringBuilderService类里面定义如下的委托。
        private delegate void ReserveHandler(string src, string dest);
然后加入委托函数和回调函数。
        private void OnReverse(string src, string dest)
        
{
            callback.ShowReerseResult(src, dest);
        }


        
private void EndAsync(IAsyncResult ar)
        
{
            ReserveHandler r 
= null;
            
try
            
{
                System.Runtime.Remoting.Messaging.AsyncResult asres 
= (System.Runtime.Remoting.Messaging.AsyncResult)ar;
                r 
= ((ReserveHandler)asres.AsyncDelegate);
                r.EndInvoke(ar);
            }

            
catch (Exception ex)
            
{
                Console.WriteLine(ex.ToString());
            }

        }
最后修改Reverse方法。
        public void Reverse(string str)
        
{
            Console.WriteLine(
"Reserve method is invoked by client.");

            StringBuilder sb 
= new StringBuilder();
            
for (int i = str.Length - 1; i >= 0; i--)
                sb.Append(str[i]);
            
string dest = sb.ToString();

            callback 
= OperationContext.Current.GetCallbackChannel<IStringBuilderCallBack>();

            ReserveHandler rh 
= new ReserveHandler(OnReverse);
            rh.BeginInvoke(str, dest, 
new AsyncCallback(EndAsync), null);

            Console.WriteLine(
"Invoke Reserve finished.");
        }
不再直接调用callback.ShowReerseResult(src, dest)了,而是通过BeginInvoke OnReverse函数来实现异步调用。

但是这样做的结果就是Service的代码比较凌乱,如果很多的Contract都需要Callback的话,就会出现很多的Delegate和回调函数。而且Service的Contract应该是独立的,他应该只负责这个Service应该做什么,返回什么结果,而不应该把Client的一些特性加进去。因为如果我们另外一个Console Application也调用这个Contract的话,就不需要这样的操作了。
而且,在Service端使用异步委托会不会造成线程安全的问题我还没有仔细考虑过。但是就我个人对SOA的理解,上面的理由就足够让我们不要再Service里面解决Deadlock问题。
在服务器端,我们异步委托了对Client的Callback操作。那么在Client端,我们就要异步委托对Service的调用,就是要把Reverse变成异步调用的Method。

首先一个前提,我们的Client不能和Serivce共用一个Contract的Reference。因为我们要在Client端重新对Contract进行包装,让他支持异步调用。简单的办法,我们通过svcutil.exe这个工具自动生成一个Contract的定义文件(例如StringBuilderService.cs),然后加入到Client的Project里面。Client Project只引用System.ServiceModel。
修改Client端的StringBuilderService.cs文件中关于Reverse的定义部分。
原先的定义是
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel""3.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName
="IStringBuilder", CallbackContract=typeof(IStringBuilderCallback))]
public interface IStringBuilder
{
    [System.ServiceModel.OperationContractAttribute(Action
="http://tempuri.org/IStringBuilder/Reverse", ReplyAction="http://tempuri.org/IStringBuilder/ReverseResponse")]
    
void Reverse(string str);
}
这是一个普通的函数,Action属性指向了这个Method将会对应到Service上面的哪一个Method实现,这里是http://tempuri.org/IStringBuilder/Reverse。
现在我们要把这一个Method变成两个,分别是BeginReverse和EndReverse。然后修改上面的Attribute。
    [System.ServiceModel.OperationContractAttribute(AsyncPattern = true, Action = "http://tempuri.org/IStringBuilder/Reverse", ReplyAction = "http://tempuri.org/IStringBuilder/ReverseResponse")]
    System.IAsyncResult BeginReverse(
string str, System.AsyncCallback callback, object asyncStat);

    
void EndReverse(System.IAsyncResult ar);
注意BeginReverse的属性里面加了一个AsyncPattern = true,他表示这个方法要使用异步调用的方式执行。同时参数也作了相应的调整。
而且void EndReverse(System.IAsyncResult ar);方法是必须的,如果没有声明那么会出现运行时错误。
最后修改我们的Client调用。
        private void btnReserve_Click(object sender, EventArgs e)
        
{
            
try
            
{
                IAsyncResult iar 
= m_StringBuilderProxy.BeginReverse(textBox4.Text, (new AsyncCallback(OnEndReserve)), null);

                ShowInfo(
"Reserve method from StringBuilder was invoked successful.");
            }

            
catch (Exception ex)
            
{
                ShowError(ex);
            }

        }


        
private void OnEndReserve(IAsyncResult ar)
        
{
            m_StringBuilderProxy.EndReverse(ar);
        }
由于我们的Reverse没有返回值,所以在OnEndReserve函数里面没有过多的操作。如果Reverse需要返回值的话,就在声明EndReserve函数的时候加入返回值即可。
这样就在Client段实现了对Service Contract Method的异步调用。
但是要注意,如果Contract有什么变化,必须要再通过svcutil.exe重新生成Client对应的Conrtact声明。不过刚才的修改就会被覆盖掉的。

程序的代码由此下载
参考资料:
[原创]我的WCF之旅(6):在Winform Application中调用Duplex Service出现TimeoutException的原因和解决方案
通过实例分析WCF Duplex消息交换
posted @ 2007-06-21 17:13 妖居 阅读(...) 评论(...) 编辑 收藏