Don't think you are, know you are

博客园 首页 新随笔 管理

 

原文地址:http://www.aspxcs.net/HTML/2353472205.html

 

 在Silverlight中所有的服务器交互都是异步的,对大多数刚刚接触的童鞋来说同是感觉别扭,废话少说,下面我们来漫谈一下这个主题。
    先说说我的经历,在之前开发的项目中,我遇到一个问题困扰我很久,对于连续两步以上的异步Service访问应该如何写才能使得代码更加优美,维护更方便,比如这样的需求,在Service中存在这样几个操作(所有代码仅为示例,切勿较真,谢谢):

  1. using System; 
  2. using System.Linq; 
  3. using System.Runtime.Serialization; 
  4. using System.ServiceModel; 
  5. using System.ServiceModel.Activation; 
  6. using System.Collections.ObjectModel; 
  7.  
  8. namespace AsyncAndSync.Web 
  9.     [ServiceContract(Namespace = "")] 
  10.     [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] 
  11.     public class TestService 
  12.     { 
  13.         public static TaskCollection Tasks = new TaskCollection(); 
  14.  
  15.         [OperationContract] 
  16.         public bool IsTaskCreated(string taskId) 
  17.         { 
  18.             return Tasks.Contains(taskId); 
  19.         } 
  20.  
  21.         [OperationContract] 
  22.         public Task CreateTask(string taskId, string taskName) 
  23.         { 
  24.             Tasks.Add(new Task() 
  25.             { 
  26.                 TaskId = taskId, 
  27.                 TaskName = taskName 
  28.             }); 
  29.             return Tasks[taskId]; 
  30.         } 
  31.  
  32.         [OperationContract] 
  33.         public Task GetTask(string taskId) 
  34.         { 
  35.             if (Tasks.Contains(taskId)) 
  36.                 return Tasks[taskId]; 
  37.             return null
  38.         } 
  39.     } 
  40.  
  41.     [DataContract] 
  42.     public class Task 
  43.     { 
  44.         [DataMember] 
  45.         public string TaskId { getset; } 
  46.         [DataMember] 
  47.         public string TaskName { getset; } 
  48.     } 
  49.  
  50.     public class TaskCollection : KeyedCollection<string, Task> 
  51.     { 
  52.         protected override string GetKeyForItem(Task item) 
  53.         { 
  54.             return item.TaskId; 
  55.         } 
  56.     } 

在客户端要执行的操作是这样的,先判断某一任务是否已经创建,如果已经创建则加载任务,如果没有创建,就询问用户是否需要新建,根据用户的选择决定后续的操作,这个需求相信大家都很容易理解,这时我习惯性的先写出伪代码:

  1. private void Test1() 
  2.     string taskId = "test id"
  3.     Task task = null
  4.     var isTaskCreated = CheckTaskCreated(taskId); 
  5.     if (isTaskCreated) 
  6.     { 
  7.         //load task 
  8.         task = this.LoadTask(taskId); 
  9.     } 
  10.     else 
  11.     { 
  12.         var result = MessageBox.Show("任务没有创建,是否立即创建""问题", MessageBoxButton.OKCancel); 
  13.         if (result == MessageBoxResult.OK) 
  14.         { 
  15.             //create task 
  16.             task = this.CreateTask(taskId, "test name"); 
  17.         } 
  18.     } 
  19.  
  20.     if (task != null
  21.     { 
  22.         //后续操作 
  23.     } 
  24.  
  25. private bool CheckTaskCreated(string taskId) 
  26.     return true
  27.  
  28. private Task LoadTask(string taskId) 
  29.     return null
  30.  
  31. private Task CreateTask(string taskId, string taskName) 
  32.     return null

 可是当我开始Coding的时候发现这些未实现的操作根本无法填充,若是在Winform下这段代码的结构没有任何问题,但由于Service的操作都是异步的,后面的三个方法根本无法立即返回结果,甚至后来我发现项目中的Confirm操作都是异步的,就是说连用户操作的确认也是异步的,因为你无法这样写代码:

  1. private bool CheckTaskCreated(string taskId) 
  2.     TestServiceClient client = new TestServiceClient(); 
  3.     return client.IsTaskCreated(taskId);//无该方法 
  4.  
  5. private Task LoadTask(string taskId) 
  6.     TestServiceClient client = new TestServiceClient(); 
  7.     return client.GetTask(taskId);//无该方法 
  8.  
  9. private Task CreateTask(string taskId, string taskName) 
  10.     TestServiceClient client = new TestServiceClient(); 
  11.     return client.CreateTask(taskId, taskName);//无该方法 

起初我还执着于有没有方法可以在上面的方法中直接返回结果,问题归结为Silverlight中是否存在同步操作,我没有google而是自己开始尝试,因为如果把上面的这个业务逻辑改成全异步代码会比较混乱,可能像这样:

  1. private Task task; 
  2. private void Test2() 
  3.     string taskId = "test id"
  4.     TestServiceClient client = new TestServiceClient(); 
  5.     client.IsTaskCreatedCompleted += new EventHandler<IsTaskCreatedCompletedEventArgs>(client_IsTaskCreatedCompleted); 
  6.     client.IsTaskCreatedAsync(taskId, taskId); 
  7.  
  8. void client_IsTaskCreatedCompleted(object sender, IsTaskCreatedCompletedEventArgs e) 
  9.     TestServiceClient client = new TestServiceClient(); 
  10.     if (e.Result) 
  11.     { 
  12.         client.GetTaskCompleted += new EventHandler<GetTaskCompletedEventArgs>(client_GetTaskCompleted); 
  13.         client.GetTaskAsync((string)e.UserState); 
  14.     } 
  15.     else 
  16.     { 
  17.         client.CreateTaskCompleted += new EventHandler<CreateTaskCompletedEventArgs>(client_CreateTaskCompleted); 
  18.         client.CreateTaskAsync((string)e.UserState, "test name"); 
  19.     } 
  20.  
  21. void client_GetTaskCompleted(object sender, GetTaskCompletedEventArgs e) 
  22.     task = e.Result; 
  23.     //后续操作 
  24.  
  25. void client_CreateTaskCompleted(object sender, CreateTaskCompletedEventArgs e) 
  26.     task = e.Result; 
  27.     //后续操作 

不知道你还能不看出来原有的业务逻辑,这里还没有考虑Confirm操作也是异步的情况,于是我做了大量的尝试,想把这个Service操作变成同步的,我尝试了几种方法,我首先将代码改成了这样:

  1. private bool CheckTaskCreatedBySleep(string taskId) 
  2.     bool? isTaskCreated = null
  3.     TestServiceClient client = new TestServiceClient(); 
  4.     client.IsTaskCreatedCompleted += (s, args) => 
  5.     { 
  6.         isTaskCreated = args.Result; 
  7.     }; 
  8.     client.IsTaskCreatedAsync(taskId); 
  9.     while (isTaskCreated == null
  10.         Thread.Sleep(100); 
  11.     return isTaskCreated.Value; 

在没有得到结果之前100毫秒100毫秒的等待,知道得到结果,看起来应该没问题,运行测试,结果是代码陷入死循环,始终在等待100毫秒,此路不通,我们换个方法:

  1. private bool CheckTaskCreatedByWaitOne(string taskId) 
  2.     bool? isTaskCreated = null
  3.     var resetEvent = new ManualResetEvent(false); 
  4.     TestServiceClient client = new TestServiceClient(); 
  5.     client.IsTaskCreatedCompleted += (s, args) => 
  6.     { 
  7.         isTaskCreated = args.Result; 
  8.         resetEvent.Set(); 
  9.     }; 
  10.     client.IsTaskCreatedAsync(taskId); 
  11.     resetEvent.WaitOne(); 
  12.     return isTaskCreated.Value; 

使用ManualResetEvent应该没问题了吧,运行测试,结果跟之前一样,始终在等待,这让我很是困惑,也是我在Winform程序中引用了同样的Service用上面的代码测试,都是可以运行的,为什么?为什么我用等待的方式去得到结果在Silverlight中是不行的?这个疑问促使我做了另外一个实验:

  1. private void CheckTaskCreatedAsync(string taskId) 
  2.     MessageBox.Show("call thread:" + Thread.CurrentThread.ManagedThreadId.ToString()); 
  3.     TestServiceClient client = new TestServiceClient(); 
  4.     client.IsTaskCreatedCompleted += (s, args) => 
  5.     { 
  6.         MessageBox.Show("callback thread:" + Thread.CurrentThread.ManagedThreadId.ToString()); 
  7.     }; 
  8.     client.IsTaskCreatedAsync(taskId); 

结果使我恍然大悟,call thread和callback thread都在UI thread上,其实我早该注意到,因为我们可以直接在Completed事件的处理函数中操作UI,而不用使用DependencyObject的Dispatcher.BeginInvoke来操纵UI,这也是Silverlight中的Wcf子集所特有的,差别不仅在回调在UI进程上,在代码模式上也存在很大差异,让我们来生成一个Winform的Service Reference来观察一下(注意在添加Service的时候将高级选项中的Generate asynchronous operations勾上),我们来对比一下:

  1. [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] 
  2. public System.IAsyncResult BeginIsTaskCreated(string taskId, System.AsyncCallback callback, object asyncState) { 
  3.     return base.Channel.BeginIsTaskCreated(taskId, callback, asyncState); 
  4.  
  5. [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] 
  6. public bool EndIsTaskCreated(System.IAsyncResult result) { 
  7.     return base.Channel.EndIsTaskCreated(result); 

在Winform下我们调用同样的Service的异步操作是分成了两个方法BeginIsTaskCreated和EndIsTaskCreated,类似于Delegate上的BeginInvoke和EndInvoke一样,在Begin开头的方法中指定的callback将在另外的thread上被回调,并在End开头的方法中返回结果;而Silverlight中生成的是一个IsTaskCreatedAsync的方法和一个IsTaskCreatedCompleted事件,结果被IsTaskCreatedCompletedEventArgs的类中包含(如果有返回值作为Result属性暴露),这是从做外层暴露的接口层看到的。通过观察,类似的Begin与End开头的方法在Silverlight中也被生成了,只不过没有Public,我尝试将方法手改成Public是不行的,编译器会提示下列错误:The modifier 'public' is not valid for this item
    我尝试到这里就觉得Silverlight是要彻底封锁Service的同步操作,所以没有再继续尝试,而是开始在Google中寻找答案,发现早就有人做了类似的尝试,原文可以查看这里,该博文的作者详细解释了为什么不支持同步操作,并且给出了一种在异步中同步的方案,在非UI thread中使用ChannelFactory动态生成代理来同步访问Service,当然,这里所谓的同步是在非UI thread中使用,在UI thread中这是不可能的,具体的实现我就不在这里赘述了,大家可以下载他的代码查看(或者关注我下一篇的介绍)。对于他提出的在异步中执行同步的思想对我启发很大,这也正是我想与大家分享的部分,我们大部分时间在编写同步的代码,进行同步的思考,当异步摆在我们眼前的时候我们也该改变编程的思路,就像文中的我一样,一系列的连续操作都是异步的,我们可以花力气去同步一堆连续的异步操作,也可以在一个异步操作中去执行连续的同步操作,听起来有些绕,但显而易见,同步一堆连续的异步操作比在一个异步操作中去执行连续的同步操作更加困难。
    通过这次的实验,我意识到在Silverlight中我们应该适应异步的编程模式,虽然我最初在挣扎,但我渐渐意识到异步操作是非常必要的,希望刚刚接触Silverlight的新童鞋们跟我一起转变自己的思路,更快更好的转换思路,希望本文对您有帮助。

posted on 2010-09-02 15:40  炭炭  阅读(1883)  评论(0编辑  收藏  举报