Silverlight使用WebService,WCF,RIA Service的同步方法
大多數接觸Silverlight程式開發的人只要有需要對後端Server作呼叫應該都會遇到一個問題,那就是所有的呼叫都是非同步機制,而沒有任何同步的方式,這個問題的主因在於Web機制上的問題,MSDN論壇中有很多篇已討論此問題且有介紹原因,但不幸的受限於底層.在Silverlight SDK中要做到同步會有些考量因素我想未來應該也很難加入,本篇文章在於如何解決這些問題.
由於Silverlight呼叫並不能同步呼叫所以必須找到一些方式來處理,熟悉MultiThread設計的人第一個想到的就是透過dotNet的Thread同步機制來處理,譬如說Thread.Join或AutoEvent等方式.但一使用這個方法馬上出現個一個問題,並無法正常運作似乎程式碼凍結在那邊不會執行
底下為範例程式碼
System.Threading.AutoResetEvent evt = new System.Threading.AutoResetEvent(false);
this.DomainContext.SubmitChanges(
(op) =>
{
evt.Set();
}, null);
evt.WaitOne();
程式執行到evt.WaitOne就停在那不會動了,但這個方式在WPF或Winform中運作的很順利,似乎Silverlight有個原因而無法運作,而這個原因就是Silverlight無法非同步呼叫的主因,先來看看原因,就會了解該如何解決了.
看以下程式碼
1 this.DomainContext.SubmitChanges(
2 (op) =>
3 {
4 .....
5 }, null);
6
7 ........
以上是個一個非同步呼叫的範例程式碼,如果試著在4與7那行做中斷追蹤,會發現一個狀況,不論執行多少次,7永遠比4先執行.即使7後面還有更多的程式碼,不論如何增加,只要在同一個函式內都是一樣,故得到一個結論,在Silverlight中同一個函式內的非同步呼叫一定於函式執行完後才會執行,這也是上面evt.WaitOne會凍結在那的原因,因為evt.Set根本無會觸發.難道這個問題在所有Silverlight函式中都會發生?其實上面說法並不完全,如果在仔細閱讀MSDN論壇的原因,真正得到的結論是-
在Silverlight中UI Thread中的函式執行非UI執行緒,則此執行緒一定於函式執行完後才會執行
也就是說這只會發生在UI Thread,所以解決方式自然就出來的上述程式碼改為如下1 new Thread(new ThreadStart(
2 () =>
3 {
4 AutoResetEvent evt = new AutoResetEvent(false);
5 this.DomainContext.SubmitChanges(
6 (op) =>
7 {
8 ....
9 evt.Set();
10 }, null);
11
12 evt.WaitOne();
13 ........
14 })).Start();
將原來的程式碼再包裝於另一個Thread中,即可解決此問題了.evt.WaitOne也能正常運作了.
但因為這種方式在撰寫程式時變得相當麻煩,如過更複雜些的執行順序將會導致程式在撰寫實的複雜度,故我另外也寫了一個SerialisedWorkQueue輔助類別來處理間化這個問題,使用方式如同底下程式碼,會在後續文章中介紹
SerialisedWorkItem wi = new SerialisedWorkItem(
(o, set) => //段落一
{
this.IsBusy = true;
this.DomainContext.Load(this.DomainContext.GetDestributionPotentialsQuery(),
(op) =>
{
…
set();
}
, null);
}).Parallel(
(o, set) => //段落二
{
this.DomainContext.Load(this.DomainContext.GetDamagePotentialsQuery(),
(op) =>
{
…
set();
}
, null);
}).After( //段落三
(o, set) =>
{
this.DomainContext.Load(this.DomainContext.GetMalwareInfosQuery().Where(m => m.Id == this._malwareInfoId),
(op) =>
{
…
this.IsBusy = false;
set();
}
, null);
});
SerialisedWorkQueue workQueue = new SerialisedWorkQueue();
workQueue.QueueWorkItem(wi);