Wcf回调之Ui客户端

当服务支持回调时,Callback契约一般使用IsOneWay=true, 在控制台的客户端下对于执行回调的服务契约(我们简称服务契约)没有多大限制,但是在UI客户端的情况下,服务契约应该怎么配置呢?我现在给大家来讨论这个问题以及如何解决:

 1 [ServiceContract(CallbackContract =typeof(ITalkCallback),SessionMode =SessionMode.Allowed)]//由于支持回调,因此必须使用支持会话的绑定。
 2     interface ITalk
 3     {
 4         [OperationContract(IsOneWay=true)]
 5         void Login(TalkMessage m);
 6     }
 7 
 8     interface ITalkCallback
 9     {
10         [OperationContract(IsOneWay=true)]
11         void BroadCast(TalkMessage m);
12     }

客户端代码

TalkClientProxy client = new TalkClientProxy(new InstanceContext(this));
TalkMessage m = new TalkMessage();
m.Name = txtUserName.Text;
m.Message = rtbTalkHistory.Text == string.Empty ? "Login" : rtbTalkHistory.Text;
client.Login(m);

服务器端代码

public static Dictionary<string, ITalkCallback> dicCallback = new Dictionary<string, ITalkCallback>();
public virtual void Login(TalkMessage m)
{
     Console.WriteLine(m.Name +"is login");
     ITalkCallback callback = OperationContext.Current.GetCallbackChannel<ITalkCallback>();
     if (!dicCallback.ContainsKey(m.Name))
     {
          dicCallback.Add(m.Name,callback);
      }

      dicCallback.Values.ToList().ForEach(c => c.BroadCast(m));
}

 

Login的IsOneWay被配置为了true,当客户端在UI线程调用服务的Login函数时有下面这几个步骤:

1.客户端调用Login将直接返回,继续执行UI代码.

2.服务开始调用Login内部代码并执行到回调的BroadCast函数,服务直接返回继续执行.

3.客户端调用broadCast函数。

由于之前客户端调用Login时已经返回,所以BroadCast函数将在客户端被执行,不会出现死锁的情况.

单UI线程下这样的配置没有问题,缺点是消息出现丢失或者执行发生异常大家都不知道。

 

1 [ServiceContract(CallbackContract =typeof(ITalkCallback),SessionMode =SessionMode.Allowed)]//由于支持回调,因此必须使用支持会话的绑定。
 2     interface ITalk
 3     {
 4         [OperationContract(IsOneWay=false)]
 5         void Login(TalkMessage m);
 6     }
 7 
 8     interface ITalkCallback
 9     {
10         [OperationContract(IsOneWay=true)]
11         void BroadCast(TalkMessage m);
12     }

Login的IsOneWay被配置为了false,当客户端在UI线程调用Login函数时会有以下几个步骤

1.客户端调用Login,等待服务执行完返回,所以客户端被阻塞.

2.服务开始调用Login内部代码,执行回调的BroadCast函数,然后直接返回继续执行.

3.客户端现在收到服务的回调通知要调用BroadCast,但是客户端也在等待Login函数的返回.

4.服务发现Broadcast并没有在客户端被执行完毕,所以服务在执行Login时无法返回.

5.客户端再等待Login返回,服务在等待BroadCast在客户端执行才能返回,因此发生了死锁.

我们来看下IsOneWay=true的MSDN说明:IsOneWay=true指定操作是单向操作,只表示它没有响应消息。 如果无法建立连接、出站消息非常大或该服务无法足够快地读取入站信息,则可能会阻塞.这样就很好的解释了第4点为什么服务在调用Login后不能直接返回,因为服务探测到回调的BroadCast有问题.

解决方法:

1.客户端新启动一个线程来调用Login函数

Task task = new Task( () => 
                {
                    client.Login(m);
                }
                );
            task.Start();

2.服务在调用Login内部代码,执行BroadCast回调,接着执行后面代码
3.BroadCast在客户端被执行, task处于阻塞状态

4.BroadCast执行完毕,服务返回,task恢复运行.

 客户端代码

 private void btnMain_Click(object sender, EventArgs e)
        {
            TalkClientProxy client = new TalkClientProxy(new InstanceContext(this));
            TalkMessage m = new TalkMessage();
            m.Name = txtUserName.Text;
            m.Message = rtbTalkHistory.Text == string.Empty ? "Login" : rtbTalkHistory.Text;

            Task task = new Task(
                () => 
                {
                    client.Login(m);        //执行服务的Login
                }
                );
            task.Start();
        }

        public virtual void BroadCast(TalkMessage m)
        {
            if (context != null)
            {
                context.Post(o => { 
                    this.rtbTalkHistory.Text += m.Name + "  " + m.Message + "\r\n";  //回调给UI控件赋值
                }, new object[0]);
            }
        }

 

看上面的描述,好像没发生死锁,但其实细心就会发现为什么UI线程执行回发生死锁而新起一个线程就不发生死锁呢?在第二情况下BroadCast应该是在task线程里执行才对,应该也要被死锁.

我带着这个疑问查看了线程ID,发现了一些问题

1.当在UI线程执行Login的时候,回调的BroadCast如果执行也是在UI线程里

2.当新启动一个task执行Login的时候,UI线程ID是8, 执行Login的线程ID是9,而执行BroadCast线程的ID是11!

太诡异了, UI线程确实是一个不寻常的线程啊,如果是在UI线程执行服务,服务的回调也会在UI线程执行,而其他线程不是,这就解释第二点为什么不会死锁了.

 

在来扯一点,如果回调函数BroadCast是调用的UI,那么在第一点中不会出现问题,但是在第二点中会报异常,因为你在非UI线程中对UI资源进行读写,因此好的方法是在Form初始化时就给一个

SynchronizationContext赋当前Form的同步上下文引用:

private SynchronizationContext context;
        public MainForm()
        {
            InitializeComponent();
            context = WindowsFormsSynchronizationContext.Current;
        }


public virtual void BroadCast(TalkMessage m)
        {
            if (context != null)
            {
                context.Post(o => { 
                    this.rtbTalkHistory.Text += m.Name + "  " + m.Message + "\r\n";                            //RichTextBox
                }, new object[0]);
            }
        }

 

这样就可以在非UI线程访问UI资源了.

 

 

 

你可能感兴趣的:(WCF)