[Silverlight入门系列]同步调用多个WCF RIA Services的异步方法

对多个异步调用要进行顺序控制是个麻烦的事情,但这也是我们经常要碰到的情形。例如,往往需要在一个异步任务完成之后才能开始另一个异步任务。所以,我们往往需要在异步任务的OnCompletedCallback里面加上下一个任务的处理,最后写出来的代码就像这样难看:

   1: var securityClient = new SecurityServiceClient();
   2:  
   3: securityClient.IsLoggedInCompleted += (s,e) =>
   4: {
   5:      var t2 = new SecurityServiceClient();
   6:  
   7:      t2.IsLoggedInCompleted += (s,e) =>
   8:      {
   9:           var t3 = new SecurityServiceClient();
  10:  
  11:           t3.IsLoggedInCompleted += (s,e) =>
  12:           {
  13:                 var t4 = new SecurityServiceClient();
  14:  
  15:                 t4.IsLoggedInCompleted += (s,e) =>
  16:                 {
  17:                      var t5 = new SecurityServiceClient();
  18:  
  19:                      t5.IsLoggedInCompleted += (s,e) =>
  20:                      {
  21:                              var t6 = new SecurityServiceClient();
  22:  
  23:                              t6.IsLoggedInCompleted += (s,e) =>
  24:                              {
  25:                                   var t7 = new SecurityServiceClient();
  26:  
  27:                                   t7.IsLoggedInCompleted += (s,e) =>
  28:                                   {
  29:                                        //TODO:
  30:                                   };
  31:                                   
  32:                                   t7.IsLoggedInAsync(); 
  33:                              };
  34:                              
  35:                              t6.IsLoggedInAsync(); 
  36:                      };
  37:                      
  38:                      t5.IsLoggedInAsync(); 
  39:                 };
  40:                 
  41:                 t4.IsLoggedInAsync(); 
  42:           };
  43:           
  44:           t3.IsLoggedInAsync(); 
  45:      };
  46:      
  47:      t2.IsLoggedInAsync(); 
  48: };
  49:  
  50: securityClient.IsLoggedInAsync(); 

不光难看,如果是个N重循环,这样写是不可以工作的。解决办法就是同步调用WCF RIA Service的方法。大家知道,WCF RIA Services的方法必须在客户端异步调用,没有同步模式。所以我们只能人工处理一下了。(请注意:本文思路参考了StackOverflow这篇回答。)

封装IAction接口:

   1: public interface IAction
   2: {
   3:     void Execute();
   4:     event EventHandler<ProgressChangedEventArgs> ProgressChanged;
   5:     event EventHandler Completed;
   6: }

异步操作类:AsynAction

   1: public class AsynAction : IAction
   2: {
   3:     private Action _action;
   4:     private readonly bool _autoComplete;
   5:     
   6:     public AsynAction(bool autoComplete = true)
   7:     {
   8:         _autoComplete = autoComplete;
   9:     }
  10:  
  11:     public AsynAction(Action action, bool autoComplete = true):this(autoComplete)
  12:     {
  13:         _action = action;
  14:         _autoComplete = autoComplete;
  15:     }
  16:  
  17:     public void SetAction(Action action)
  18:     {
  19:         _action = action;
  20:     }
  21:  
  22:     public void Execute()
  23:     {
  24:         if (_action == null) return;
  25:  
  26:         _action();
  27:  
  28:         if (!_autoComplete && Completed != null)
  29:         {
  30:             Completed(this, EventArgs.Empty);
  31:         }
  32:     }
  33:  
  34:     public event EventHandler<ProgressChangedEventArgs> ProgressChanged;
  35:     public event EventHandler Completed;
  36:  
  37:     public void OnCompleted()
  38:     {
  39:         if (Completed != null)
  40:             Completed(this, new EventArgs());
  41:     }
  42: }

对多个异步任务的列举控制类:AsynActionsEnumerator

   1: public class ProgressChangedEventArgs : EventArgs
   2: {
   3:     public int Progress { get; set; }
   4:     public string Status { get; set; }
   5: }
   6:  
   7: public class AsynActionsEnumerator : IAction
   8: {
   9:     readonly IEnumerator<IAction> _enumerator;
  10:  
  11:     public AsynActionsEnumerator(IEnumerator<IAction> enumerator)
  12:     {
  13:         _enumerator = enumerator;
  14:     }
  15:  
  16:     public event EventHandler<ProgressChangedEventArgs> ProgressChanged;
  17:  
  18:     private void OnProgressChanged(int progress, string status)
  19:     {
  20:         if (ProgressChanged != null)
  21:             ProgressChanged(this, new ProgressChangedEventArgs { Progress = progress, Status = status });
  22:     }
  23:  
  24:     public event EventHandler Completed;
  25:  
  26:     private void OnCompleted()
  27:     {
  28:         if (Completed != null)
  29:             Completed(this, new EventArgs());
  30:     }
  31:  
  32:     private int _index = 0;
  33:  
  34:     public void Execute()
  35:     {
  36:         //If the enumerator gives us another action,
  37:         //hook up its Completed event to continue the
  38:         //the chain, then execute it.
  39:         if (_enumerator.MoveNext())
  40:         {
  41:             _enumerator.Current.Completed += delegate { Execute(); };
  42:             OnProgressChanged(_index++, String.Empty);
  43:             _enumerator.Current.Execute();
  44:         }
  45:         else     //If the enumerator didn't give us another action, we're finished.
  46:         {
  47:             _index = 0;
  48:             OnCompleted();
  49:         }
  50:     }
  51:  
  52: }

添加多个异步任务:

   1: private IEnumerator<IAction> GetAsycActionsEnumerator()
   2: {
   3:    for(int i = 0; i < 10; i++)
   4:    {
   5:        var task = new AsynAction();
   6:        task.SetAction(() =>
   7:        {
   8:            var securityClient = new SecurityServiceClient();
   9:  
  10:            securityClient.IsLoggedInCompleted += (s,e) =>
  11:              {
  12:                  //TODO: your logic here
  13:                  //.......
  14:                  task.OnCompleted();
  15:              };
  16:  
  17:            securityClient.IsLoggedInAsync(); 
  18:       });
  19:       yield return task;            
  20:    }
  21: }

同步执行多个异步任务:

   1: var emu = new AsynActionsEnumerator(GetAsycActionsEnumerator());
   2: emu.Execute();        

使用AutoResetEvent或Queue来实现

另外一个很容易想到的思路自然是使用Queue或者线程同步(例如:AuteResetEventManualResetEvent等),代码例如:

   1: private resetEvent = new AutoResetEvent(false);
   2:  
   3: private void Process()
   4: {
   5:         var securityClient = new SecurityServiceClient();
   6:  
   7:      securityClient.IsLoggedInCompleted += (s,e) =>
   8:        {
   9:          //TODO: your logic here
  10:          //.......
  11:          resetEvent.Set();
  12:      };
  13:  
  14:      securityClient.IsLoggedInAsync(); 
  15:      resetEvent.WaitOne();
  16:      
  17:      //TODO: some more work....
  18:      
  19: } 

 

 

使用Queue思路也是差不多,加上一个Enque/Deque而已。

为什么上面的代码在Silverlight和WCF RIA Services下不工作?

上面的代码(AutoResetEvent, Queue)很简单,但运行一下便知道以上代码在Silverlight4+WCF RIAServices中根本不工作。为什么?因为WaitOne会阻塞主UI线程,并且会阻塞WCF RIAServices的回调(callBack),也就是说,WCF RIAServices的回调(callBack)只有在Silverlight的UI主线程没有阻塞的情况下才工作。Silverlight不足的地方就在此,(至少对Silverlight4-还是如此,Silverlight5会改进)。事实上Silverlight对WCF RIAServices的通信调用发生在UI线程上,每次只可以有一个call,不能并行调用WCF RIAServices,不信你可以用Fiddler跟踪一下。而Silverlight的UI主线程要负责的事情可多了:处理用户输入输出、运行动画等等。这不可避免产生主线程阻塞,甚至动画卡壳,这是不可避免的,因为主线程要负责太多事情了,而它在同一时刻只能做一件事情(单线程)。总之,这是Silverlight4的局限性和WCF RIAServices的局限性,还好,微软会在SIlverlight5中改善这些,引入主UI线程以外的复合线程(须GPU支持,具体看这贴)。

总结

异步任务处理和控制等在.NET 4.0下面非常简单,微软提供了很多强大的东东来处理它,什么ConcurrentQueue, PriorityQueue, TPL…..但在Silverlight下就受限制了,Silverlight本身只是一个客户端模型,使用一个受限的.NET Framework,而WCF RIA Service又是一个减配版的WCF Service。本文就是在此背景下的问题而写的。

posted on 2011-11-08 13:52 Mainz 阅读(1004) 评论(12) 编辑 收藏

导航

公告

统计