[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或者线程同步(例如:AuteResetEvent,ManualResetEvent等),代码例如:
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。本文就是在此背景下的问题而写的。
