本文章是由機器翻譯。

經驗學習

A 的大規模軟體 + 服務的應用程式的最佳化

Udi Dahan

本文將告訴您:
  • WCF 和 3 層服務模型
  • 處理偶發性的連線
  • 執行緒和可用性
  • 同步處理網域
本文將使用下列技術:
WCF、 軟體 + 服務

內容

永遠設定為日期
2-層問題
WCF 和 3-層
延展性問題
無法在 Paradise
偶而連線會影響伺服器
threading 和可用性
用戶端的 WCF
物件的 Graph 的相依性
同步處理網域
檢視-控制器互動
執行緒安全的資料繫結
雙向 DataBinding
用戶端的存放庫
效能
經驗學習

設計豐富型,互動式桌面用戶端軟體從未更容易。 使用 Visual Studio、 Windows Presentation Foundation (WPF) 和 WPF (Prism) 的複合應用程式指南,開發人員會比以往更好配備的。

不幸的是,並不足夠。

在大規模的豐富型用戶端 (Rich Client 軟體 + 服務的應用程式,開發偶爾連線,硬碟的方式出找不到我的小組使用的可以不要鎖死或產生記憶體回收資料-競爭的多執行緒、 智慧型用戶端時從一般。 不是的失敗,以在的工具組中雖然。 我們發現這些工具必須採用非常特定的模式中,以達到穩定和回應的系統。

本文中,我反白顯示一些問題,我們遇到,並說明我們如何 overcame 它們。 希望您可以利用此資訊以避免我們的錯誤,並建置您自己的軟體 + 服務的應用程式時,請進行這些工具的最佳使用。

永遠設定為日期

第一個我們所面臨的挑戰之一是需要使用者永遠會保持最新狀態與所有其他的使用者所做,以及在其他系統中發生的事件時。 這個即時系統的行為會允許這些發生在大量資料,在事件上執行動作的使用者。 舊版系統允許它們搜尋、 排序和篩選器,但使用者就無法處理該項資訊的時間。

在使用者經驗設計處理序的框格,在舊版系統的許多將已由取代 Messenger-類似快顯訊息 (Toast) 通知使用者的重要事件所。 使用其他啟用的變更者設定不同的臨界值的實體內容的超過時, 取出 toast 郵件中。 通常包含 「 toast 連結的按一下時, 開啟表單,顯示使用者在受影響的實體 — 一些具有一個之前和之後檢視。

透過調整標準要求 / 回應模式,我們已遇到使用方式的想法時我們金鑰的利害關係者會隨附在並提醒我們: 「 介面事件的世界中,使用者即時這個能力我們知識庫 」 工作者,以支援在協同作業的重要,我們要建置即時的企業 」。

簡,很清楚我們需要某種發佈 / 訂閱功能。 嘗試在事件上數百個就輪詢中央資料庫的使用者支援兩個第二個使用者可視性不是要使用 (它中斷往下約 60 的使用者)。

在頂端所有,我們的使用者擴展會是 Mobile — 其中有些中斷連線並且重新連線每隔數秒,它們所移動的 Wi-Fi 區域的其他離線工作的一小時或兩個同時 telecommuting,和某些執行大量離線工作的到一週。

我們必須 juggle 偶而連線能力資料的同步處理,並將所有在同一時間發行 / 訂閱。 我們我們無法解決所有問題,可能是用戶端或 Server-Side 但而學到在一邊的任何變更都需要對應的變更,另一端因為需要的整合式的方法。 在這的篇文章中,我將說明此程序,從伺服器端使用轉送用戶端啟動。

2-層問題

我們原始的部署模型都是傳統的 2 層模型在許多豐富型用戶端應用程式中找到。 我們必須使用這個模型的困難度已出通知中,從資料庫發送至用戶端。

我們的其中一個選項是已經與透過預存程式之後認可它們的交易會對外呼叫資料庫伺服器裝載 Web 服務完成資料庫的所有互動。 這是通知用戶端藉由傳遞它們,以及已將其傳遞至預存程序的引數叫用的預存程序的名稱發生什麼事,Web 服務的責任。

沒有正常出如此。 發生這種方法的兩個重要問題 — 一個邏輯、 其他實體。

在邏輯的問題,是單一的預存的程序無法由叫用各種的程式碼表示不同的項目的路徑在不同的時間。 當預存程序的啟動的用戶端接收到通知時,不是永遠清除什麼,是與哪些 Toast 到使用者的快顯。

在的實際問題會是我們需要公開它自己的 Web 服務,Web 服務資料庫伺服器上的會呼叫每個用戶端。 這是快速 vetoed 為 gaping 的安全性漏洞,安全性專業人員以及法規稽核人員是 (指出擔心使用者資料的來源無法一定要追蹤的即時決策者。

WCF 和 3-層

如果要解決邏輯的問題,我們發現我們需要是我們的服務合約的相關更明確的 2 層解決方案 — 說明的名稱執行預存的程序及其參數已明確發生事,但不會為什麼它發生。

當我們開始設計我們 Windows Communication Foundation (WCF) 服務合約,成為清除它不是足夠只在適當的命名和我們的服務方法的範圍設定的焦點。 我們要通知的合約明確需要-用戶端回呼叫伺服器的方式。 WCF 的回呼合約會啟用我們採取相同數量的小心的伺服器到用戶端郵件規則的服務合約。 (若要閱讀更多有關 WCF 的回呼合約,請參閱 Juval 將的文章,從 2006 年 10 月," 您需要到瞭解關於單向呼叫、 回呼和事件.")

每個命令 / 事件組已建立模型,如下所示:

[ServiceContract(CallbackContract = 
  typeof(ISomethingContractCallback))] 
interface ISomethingContract {
  [OperationContract(IsOneWay = true)] 
  void DoSomething();
}

interface ISomethingContractCallback {
  [OperationContract(IsOneWay = true)] 
  void SomethingWasDone();
}

若要啟用我們不是目前方法的叫用的用戶端回呼的 WCF 服務,我們必須裝載用戶端的程序不同的服務中,再將該處理序項目部署到自己的伺服器在本質上,將移至 3 層部署。

雖然它建議我們放在服務與資料庫系統管理員 」 有關服務可能需要立即珍貴的資源,進而影響的資料庫效能資料庫相同的方塊。 加入的有單獨的層,我們沒有狀態 (Stateless) 的 WCF 服務的好處是我們無法輕易地調整它,以更多的電腦而不需要任何 tinkering 與資料庫伺服器)。

不幸的是,不是我們認為一樣簡單。

延展性問題

我們遇到兩種延展性的問題 — 邏輯和實體的為之前。

邏輯的延展性問題會顯示本身為系統獲得更多的功能。 我們先開始開發時時, 我們必須只包含其上的所有方法在單一伺服器合約與單一的回呼合約與所有對應的方法,在其上。 我們快速地遇到的太多的開發人員 Chefs 磚塊牆攪拌相同的緩衝器。 將每個命令 / 事件組移動至它自己的服務 / 回呼合約組之後,該問題已解決。 尚未解決方案會引入另一個、 多 insidious 的問題。

當並決定来有多個服務 / 回呼合約組,我們認為主要的問題會有合約的無法管理的數目。 實際上沒有證實為大部分的問題,在應用程式的企業網域之間,模組化降低,非常好。 而問題在於,「 組 」。

一個命令可能造成的通知,多種,而且通知的一種可能由許多類型的命令所引起。 初始狀態的一對一的對應,我們之前會是過度簡化,以發展在多對多對應到所需。 不只是我們縮放指令和通知的數目,在系統中,我們需要調整它們之間的關係數量。 它看起來像一對一的表示支援 WCF 回呼合約不是要可以採取我們轉寄。

當從我們的 WCF 服務層的縮放時,我們遇到實體的延展性的問題。 我們需要一些共用的用戶端回呼合約訂閱者清單的方法。 我們盡力透過指引中所提供, Juval Lowey 2006 年 10 月 MSDN Magazine 文件我們會建立相關的基礎結構片段,其中包括出版物 / 子服務。 我們發現單一 pub / Sub 服務未能夠處理推入從所有的發行者 constituting Publisher 中的瓶頸,訂閱者端的通訊,在本質上所有訂閱者所需的通知的數量。 而且,不同的通知會有不同的優先順序 — 難彎曲基礎結構以支援的項目。

之後進行來回主題數次,最佳 pub / 子服務,我們想出的結構是有單一片段 pub / Sub 基礎結構的每個邏輯的企業網域。 因為每個網域,也有的命令和 clumped 其周圍的通知集,它會提供做為良好的初始狀態邊界。 這個磁碟分割,解決許多問題,我們先前有時自動建立第一層 pub / Sub 基礎結構向外的優先順序。

無法在 Paradise

WCF 的優點之一就是各種技術,例如 HTTP 和 TCP 上的多個繫結支援。 原始我們選擇使用 WS HTTP 因為它是在大部分的功能豐富,但不考慮決策的所有影響。

我們到我們的使用者展系統,一切好像會正常,但本報簡短的感覺,是。 已在後約一小時,我們開始提取呼叫從我們的支援工程師人 「 伺服器已拒絕連線 」。

我們快速地檢查,並看到我們所有的伺服器已設定,並執行,但是沒有似乎執行大部分。 CPU 使用量都是低、 記憶體,IO,和所有其他的相同。 我們檢查死結 (Deadlock) 和其他 nasties,資料庫,但一切清除有,太]。

因此,我們啟動另一個伺服器處理序,在偵錯模式中,並開始監視以查看它。 沒有花費長的時間顯示本身的問題。

每隔 10 到 20 秒,執行緒已發行到用戶端的事件,會停止回應約 30 秒。 您可以想像這沒有我們的延展性。 因為我們很是接近我們所支援的用戶端的數,建議使用的執行緒集區大小,我們所出的執行緒中執行。 在該點 WCF 會啟動拒絕接受來自用戶端的新連線。

也許我們認為我們有用戶端應用程式造成封鎖的伺服器的錯誤。

我們去尋找在特定的用戶端電腦造成問題 (使用我們出中從伺服器記錄檔選取 IP 位址)。 我們曾在這些電腦的使用者,如果它們有發生任何奇怪的行為,在應用程式在伺服器問題的時間前後看到。 它們沒有報告任何的一般。 但其中所做的有趣的觀察值之一: 「 應用程式傾向於堵塞 Outlook 進行,出剪下的 [我的 WiFi 連線時間前後的權限,方法 」。

偶而連線會影響伺服器

我們開始收集記錄檔從數個用戶端和伺服器的機器,並不斷地看到相同的行為。 用戶端離任何時間呼叫它會封鎖 30 秒。

這個計數器以我們瞭解 IsOneWay 屬性上執行 [OperationContract。 我們所有事件通知方法都傳回 void — 時沒有遭到封鎖,伺服器執行緒的理由。

我們切入 WsHttpBinding 的行為至更深層,我們開始瞭解它如何運作單向作業的合約。 當伺服器會通知用戶端叫用其 Proxy,單向方法,如果先前已連接的 Proxy,且具有其快取中的使用者所有基礎的通道物件,會使用它們,,嘗試透過 HTTP 呼叫用戶端。 即使呼叫是單向,基礎的通道等待可以建立 HTTP 連線,且可以傳送 HTTP 要求。 它不會等待 HTTP 回應。 不幸的是,如果基於任何理由用戶端是離線,HTTP 連線會放棄之前,先等待預設的 HTTP 逾時 (30 秒)。

瞭解我們無法修正這個問題,我們需要尋找會中斷連線的用戶端表面的健全的替代繫結。 我們在 Microsoft Message Queuing (MSMQ) 的繫結中找到答案 (特別是 NetMsmqBinding。 幸好,與 WCF,交換為另一個的繫結不是什麼大不了。

確認我們的伺服器執行緒已未取得停留更時用戶端的連線發生中之後,我們開啟我們的 sights 傳回至用戶端。

threading 和可用性

設計豐富的用戶端與伺服器以要求 / 回應的方式互動時, 執行緒監護過大的考量。 伺服器,我們開發了更多的功能,並變得更強大的回應所花的時間以及成長。 這已知會時應用程式停止回應,直到伺服器的回應會造成可用性問題。 常見的解決方案是,讓它以非同步方式呼叫伺服器,請變更用戶端。 在該點開發人員瞭解從伺服器的回呼在背景執行緒上處理的時, UI 控制項傾向於擲回在它們的例外狀況。 最後,程式碼會變更,因此伺服器的回呼會切換至前景執行緒,並將所有再次運作。

重點是,用戶端會收到一個無止盡的資料流的通知,從多個伺服器時, 這些模式不提供我們也。

因為在前景的執行緒上處理每個到達用戶端的通知,而且所有的 UI 互動也在前景執行緒上完成這些兩個工作最後持續下去的控制項。 實際上,您無法看到不穩跳在螢幕上,當您嘗試移動滑鼠。 輸入至文字方塊時,字元會就不穩填寫。

我注意到這時花費一些時間,在我們的負載測試實驗室中。 我記得查看顯示非常滿意出我們的架構運作方式的實際時間的設定視窗的所有各種通知用戶端。 我在其中一個 toasts 連結按一下滑鼠,保持取得我移動它停留。 它提醒我的無線的滑鼠運作其電池骰子的方式。 但滑鼠標準的有線的 USB 滑鼠。

我的 winced。 我們只能從發行兩週。 我們所有的功能性測試已傳遞,並負載測試已顯示系統無法處理所有我們在擲回。 唯一的問題已系統未在負載下完全功能 — 可用性的夢魘就已較佳的描述。

用戶端的 WCF

之前回到繪圖板上我們的用戶端,我們討論的東西 — 任何 —,可以防止即將發生的重新寫入。 我們 pored 透過如何使用 WCF 智慧型用戶端環境 (請參閱 MSDN 中的指引 使用 Windows Communication Foundation 中撰寫智慧型用戶端「) 和撰寫概念多個驗證程式碼比過之前,但以某種方式執行任何動作會涵蓋所有在基底。 似乎我們所停留死結搖滾之間無法使用的硬碟位置。

一個策略,看起來了,根據安全封裝與 Windows Form 同步處理內容 (參考) 方式封送即使它們由在背景執行緒所存取的它們處理呼叫回至 UI 執行緒之間的互動的控制項。

因為不是每個到達用戶端的通知,包括更新 UI,我們可以取得大量的可用性藉由執行大部分的用戶端通知,在背景執行緒上處理。 偶爾,通知牽涉到更新 UI 時, 可確保安全的控制項,以在實際的 UI 呈現了正確的執行緒上。 technologically,它看起來像一個穩固的解決方案。

往前及實作所需的安全控制項和表單之後, 我們會花在執行負載測試在背景中執行時的功能性測試的測試實驗室在正確時間。 我們必須採取用戶端上的所有記錄,並讓非同步以及,讓我們可以讓高的記錄等級,而減緩 UI。

在這個財務應用程式,多個商人會共同作業在單一的投資組合,來達到各種不同的目標方面的委員會的風險,並傳回上 (有時候會在單一的交易上共同作業。 我們必須模擬此行為的數個測試人員時, 在其中一項在執行它們必須那麼負值傳回。 雖然這並不是一定驚人,本身 (我們面對它,如果軟體測試人員無法做一個商人的工作,它們就不會是軟體測試人員,) 事實上它是因此對我們需要注意的其他執行的結果不同。 此系統的開發我們先前經驗會教導我們我們發現了不尋常的東西時, 它是我們在我們的程式設計中做了一些假設開啟時為 false。

sifting 透過記錄檔的 piles,我們尋找目前,其中一個執行財務傳回採用一個 DIP。 令人驚訝的是,了所有太清楚-發生一個清除的點,傳回進入紅色的位置。 我們會盡力我們的方式,透過記錄檔項目,該點的來回時間,任何好幾出。 所有查詢幾乎像它傳回正時。

我們的 DBA 的 old-time 的 UNIX 駭客的其中一個當然我與某些 regex (規則運算式 (Regular Expression) 中,他找一個小時的核心差異。 因為我們已經至,3 小時快速轉讓,45 分鐘後,他 resurfaced 的一個帶。

它是內容切換,在最差可能,和它在最後導致資料競爭。 一個執行緒已將交易將設定為 (約 9000 萬 ¥,,在其他已設定為 1 百萬美元,一個屬性,在時間]。 不幸的是,交易結束設定 1 百萬 ¥ 或 $ 11,000 USD 的狀態。 說明,在財務傳回 DIP。

在同一時間使用相同的物件時,防止多個執行緒無法火箭向。 每個執行緒只需要鎖定物件之前使用它 (請參閱 [圖 1 ) 中。 這需要完整的大量的用戶端程式碼,以確定我們有鎖定所有我們需要通過。 然後,它需要測試,以讓我們鎖定比我們需要從產生死結出一個相當數量。

[圖 1 鎖定物件

//Thread 1
void UserWantsToDoSomethingToTrade(
    Guid tradeId, double value, Currency c) {
  Trade t = InMemoryStore.Get(tradeId);
  lock(t) {
    t.Value = value;
    t.Currency = c;
  }
}

//Thread 2
void ReceivedNotificationAboutTrade(
    Guid tradeId, TradeNotification tn) {
  Trade t = InMemoryStore.Get(tradeId);
  lock(t) {
    t.Value = tn.Value;
    t.Currency = tn.Currency;
  }
}

我們 (非常 reluctantly) 給設定的項目之一是資料繫結到使用者可編輯檢視的記憶體中的物件時。 我們無法讓使用者表單之後,無法封鎖等待處理所有其他的通知的物件背景執行緒所開啟的期間鎖定物件。

以相當的 trepidation 量,我們將系統透過舊電池的測試,等待,無可避免 「 其他 」 我們不知道,會破壞其英呎的專案。 一次。

我們軟體測試人員的商人工作系統時,它看起來像上述的問題有已解決。 更多使用者] 和 [更多的類型,使用者的更深層和更相關案例是透過執行的互動相同的投資組合 — 變更風險的設定檔,執行 what-if 預測和歷程記錄的比較。 軟體測試人員真正 banged 從每個方向在系統上,並它所保留。 半 disbelieving 這可能實際上是它,我們來到回企業的好消息。

他們相信我們不。 我是說我們的追蹤記錄不是特別令人印象深刻在該點。 120%晚期傳遞會 erode 如此的信賴。 但它們在恐怖的需要,新系統的讓該星期二我們復原到 Beta 版。

並在星期四,我們復原出。

物件的 Graph 的相依性

在單一測試回合的獲利性時 grossly 不同於其他測試執行,甚至財務 novices 類似開發人員和軟體測試人員將採用注意事項。 更 delicate 投資和交易規則違反了,但那些沒有即時運算] 或 [大規模的衝擊時, novices 沒有看見它。 專家執行。

我不會進入 nitty gritty 財務規則這裡,但技術問題我們單一物件鎖定的策略是太簡化為包含多個物件的規則。

很難。

您看到,當我們呼叫的程式碼鎖定,並更新一個物件,該物件無法引發的事件會由多個其他物件所處理。 這些物件的每一個可能選擇自行更新,並引發事件的它自己的這個處理序 percolating 出影響許多物件。 不用說我們呼叫的程式碼無法知道這些其他物件將不會已鎖定,因此,這些 ripples 會跳至距離。 再次,我們還會有多個更新相同的物件沒有鎖定的執行緒。

沒有討論的以便我們無法取得某些執行緒] 安全但不必重新實作所有我們複雜的規則,我們花那麼久的時間取得權限),思考,設定讓事件和其他彈性的通訊機制上沒有.NET 的優點是至請過多。

它可能不是只是網域物件。 控制站物件無法取得呼叫回以及,變更其狀態,太,停用及啟用工具列按鈕和功能表,移除 toast,設定您命名它。

我們需要單一全域鎖定,但一個無法使用的 UI 的回憶做我們這類解決方案的注意。 也是即使這類鎖定存在,我們就得檢閱每一行程式碼,以確保已採取鎖定,並適當地釋放系統中,考量,不只是這個軟體版本但每個單一的維護的發行和補充程式之後。

如果您執行這項,damned 如果不這麼做,是 damned 的情況。

這時我們發現同步處理網域。

同步處理網域

同步處理網域是以宣告方式提供執行緒存取物件的自動同步的處理。 這個類別是支援.NET 遠端服務基礎結構的一部分引入的。 開發人員想要表示類別是能夠存取其同步處理的物件必須有類別繼承自 ContextBoundObject,並將使用 SynchronizationAttribute 標記它會像這樣:

[Synchronization]
public class MyController : ContextBoundObject {
  /// All access to objects of this type will be intercepted
  /// and a check will be performed that no other threads
  /// are currently in this object's synchronization domain.
}

如預期般,所有此魔法會有一些效能負荷物件的建立和存取。 另一個惱人的警告是在同步處理網域中相關的類別不能有泛型方法或屬性,雖然它可以呼叫並否則請使用從其他類別的泛型。

這時候專案中, 我們就幾乎耗盡所有的其他選項時我們提供它在擷取畫面。

我們計劃在有一個單一的同步處理網域,在其中會執行所有的邏輯。 這意味著控制站物件、 網域的物件和用戶端端 WCF 的物件會需要在同步處理網域中。 事實,唯一,就必須在同步處理網域以外的物件會是在 Windows Form 本身 」、 「 他們的控制項與 「 任何其他的視覺的 GUI 項目。

有趣的東西我們發現,是同步處理網域中不是所有的物件必須繼承自 ContextBoundObject,或在已套用至它們 SynchronizationAttribute。 而,我們僅需要這樣做的同步處理領域的界限上的物件。 這意味著我們所有的網域物件仍然為之前 — 一個大的效能提升。

控制器類別都需要只要多注意。

檢視-控制器互動

在我們模型-檢視-控制器 (MVC) 模式的使用,控制站會與 UI 執行緒上,只檢視互動。 這是不同的方法,不是安全的控制項先前說明的控制器無法呼叫在背景執行緒上的檢視。

我們也知道,從伺服器接收到不是每個通知所需的更新的 UI,但它是要決定該控制站物件的責任。 因此,控制站需要處理告知在背景執行緒上,,而且如果需要,更新 UI 會負責切換執行緒。

一小件事,要牢記在心雖然,是永遠都以非同步方式切換執行緒,否則您可能發生死結您的系統,或會造成嚴重的效能 (說從經驗中) 的問題。

我們建立一個控制器基底類別,封裝在執行緒,叫用 (Invoke) 使應用程式程式碼簡單 (請參魷 \ 的位元更新的版本 [圖 2] ).

[圖 2 控制器的基底類別

[Synchronization]
public class MyController : ContextBoundObject {
  // this method runs on the background thread
  public void HandleServerNotificationCorrectly() 
   {
  // RIGHT: switching threads asynchronously
  Invoker.BeginInvoke( () => CustomerView.Refresh() );

  // other code can continue to run here in the background

  // when this method completes, and the thread exits the
  // synchronization domain, the UI thread in the Refresh
  // method will be able enter this or any other synchronized object.
   }

   // this method runs on the background thread
   public void HandleServerNotificationIncorrectly()
   {
  // WRONG: switching threads synchronously
  Invoker.Invoke( () => CustomerView.Refresh() );

  // code here will NOT be run until Refresh is complete

  // DANGER! If Refresh tries to call into a controller or any other
  //         synchronized object, we will have a deadlock.
   }

   // have the main form injected into this property
   public ISynchronizeInvoke Invoker { get; set; }

   // the view we want to refresh on server notification
   public ICustomerView CustomerView { get; set; }
}

我們真的希望回的一件事不過,是資料繫結。 您不太可以說出您正在做 MVC,是否您的模型為字串、 雙精度浮點數 (Double 和 ints 的集合。

執行緒安全的資料繫結

我們之前使用資料繫結,問題是,我們檢視物件會保留允許更新相同的物件已由背景執行緒更新 UI 執行緒的模型物件的參考。 許多我們開始放入位置 ViewModel 模式,變得更簡單,就需要這些物件的結構的獨立控制所做的所有差異。

我們在上一步將資料繫結然後的下一步是有傳遞 [複製] 至其檢視的控制站物件就像這樣:

public class CustomerController : BaseController {
  // this method runs on the background thread
  public void CustomerOverdrawn(Customer c) {
    ICustomerOverdrawnView v = this.CreateCustomerOverdrawnView();

    v.Customer = c.Clone(); // always remember to clone

    this.CallOnUiThread( () => v.Show() );
  }
}

雖然這個技術上來說過,但發生一些問題。 第一個問題已可維護性 — 如何無法確保所有開發人員記得複製其網域物件,然後再將它們傳遞到檢視? 第二個問題都已詳細的技術 — 網域物件參考,因此只會複製一個其他已複製不表示彼此。

簡,我們需要網域物件,是深層複製,因為它們已傳遞到檢視。 也是很重要的我們沒有移到這種複雜性的任何檢視表本身。 我們需要是,您若要檢視的一般 Proxy 在 CreateCustomerOverdrawnView,和中建立的 Proxy 檢查所有的方法呼叫,並且參數的網域的物件的屬性 Setter 會執行一個深層的複製品,並接著將該複製品傳遞至本身的檢視。

有許多技術可讓您執行此 Proxy 處理],而且每個執行不同的動作。 某些使用外觀導向程式設計技巧,其他人更直接攔截,但本身,策略不重要。 只要知道您需要建立 Proxy 中。 在的 Proxy 中包括 [一個深層複製的方法和伴隨的字典,來存放複製的工作集]。 [圖 3 ] 顯示我們的解決方案。

圖 3] 的 [檢視] 中複製物件

// dictionary of references from source objects to their clones
// so that we always return the same clone for the same source object.
private IDictionary<object, object> sourceToClone = 
  new Dictionary<object, object>();

// performs a deep clone of the given entity
public object Clone(object entity) {
  if (entity.GetType().IsValueType)
  return entity;

  if (entity is string)
  return (entity as string).Clone();

  if (entity is IEnumerable) {
    object list = 
      Activator.CreateInstance(entity.GetType()) as IEnumerable;
    MethodInfo addMethod = entity.GetType().GetMethod("Add");

    foreach (object o in (entity as IEnumerable))
      addMethod.Invoke(list, new object[] {Clone(o)});

    return list;
  }

  if (sourceToClone.ContainsKey(entity))
    return sourceToClone[entity];

  object result = Activator.CreateInstance(entity.GetType());
  sourceToClone[entity] = result;

  foreach(FieldInfo field in 
    entity.GetType().GetFields(BindingFlags.Instance | 
    BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | 
    BindingFlags.Public)) field.SetValue(result, 
    Clone(field.GetValue(entity)));

  return result;
}

這個方法會透過所有的屬性和物件,複製它們需要的欄位中。 如果找到先前複製的物件參考在字典中,它不會再次,複製,但會傳回第一個的複製品。 這種方法會建立在指定的圖形物件,防止檢視物件,可能會在背景執行緒上被用來存取的鏡像影像。

所有這已完成之後,我們檢視無法現在繫自由結至指定的網域物件。 為何我們無法不再依賴是雙向資料繫結自動更新繫結檢視表的網域物件。

雙向 DataBinding

在一般的資料繫結案例,變更繫結的物件時, 它會引發事件,讓瞭解您應該重新整理檢視。 這其實有助於分離系統組件的處理與伺服器的通訊不需要更新的任何檢視,當他們更新網域物件。 在我們新的安全執行緒 (Thread-Safe 架構,在用戶端端 WCF 的物件不更新和繫結到的檢視的網域物件的相同執行個體,因為我們損失一些雙向資料繫結的優點。

很重要,要瞭解在多執行緒環境中,我們必須提供雙向資料繫結。 如果在背景執行緒更新網域物件繫結至 UI,INotifyPropertyChanged (參考) 的行為就會讓背景執行緒直接更新 UI 和損毀應用程式。

控制站現在必須採取的知道哪個檢視必須在更新時。 例如時使用者擁有的表單,開啟 [顯示一個暫止的交易的詳細資訊,用戶端接收有關交易的變更通知, 相關的控制器應該更新表單。 以下是如何我們它原來的方法:

public class TradeController : BaseController {
  public void Init() {
    Commands.Open<Trade>.Activated += (args => {
      TradeForm f = OpenTradeForm(args.Trade);

      args.Trade.Updated += this.CallOnUiThread(
        () =>  f.TradeUpdated(args.Trade)
      );
    });
  }
}

這個程式碼會處理交易的泛型的開啟命令,開啟表單,並在顯示要求的交易。 它也會指定交易更新時, 表單傳遞更新的交易。

我們 well-pleased 這個初始狀態、 簡單,和簡單的程式碼。

也就是,直到我們發現它有錯誤中。

任何先前的交易會被更新時,使用者開啟其第二個交易 (或之後的任何交易) 時, 表單會顯示更新的交易,即使使用者不在乎它再。 我們需要為更謹慎地在我們的回呼的處置:

public class TradeController : BaseController {
  public void Init() {
    Commands.Open<Trade>.Activated += (args => {
      TradeForm f = OpenTradeForm(args.Trade);

      Delegate tradeUpdated = this.CallOnUiThread(
        () =>  f.TradeUpdated(args.Trade)
      );

      args.Trade.Updated += tradeUpdated;

      f.Closed += () => args.Trade -= tradeUpdated;
    });
  }
}

這裡差異會是我們保留的委派 (Delegate) 的參考,讓我們可以在表單關閉時取消訂閱交易) 更新。 修正的錯誤。

除了的小一件事。

這個程式碼可以在單一執行緒系統,正確,但是我們多執行緒的用戶端的環境嚴苛的複製品戰爭中, 參考不一定您認為]。

來自某個地方的 eventargs 中的交易物件-特定命令的啟動。 命令會啟動使用者在 UI 執行緒上, 為使用者所做的事的結果,在這種情況下連按兩下 [框格中的交易。 但是,您也可以控制器必須讓它該檢視,而且在處理序中,交易會有已複製,如果交易顯示在方格中,表示。

簡,從伺服器交易就無法直接使用複製的物件在方格中,更新的事件控制器上方訂閱相關的任何通知後就不會取得引發。 因此,顯示交易的詳細資料,表單會無法取得更新。

項目的解決方案中遺失。

用戶端的存放庫

我們的控制站將需要您,某些方法尋找出時取得更新的實體的特定執行個體,但不使用依賴物件參考。

如果每個實體都具有一個的識別項,並且發生在用戶端無法被查詢這些實體記憶體中的存放庫],控制站無法使用它來取得授權的實體,根據來自 UI 的識別項的參考。

以下是我們的貿易控制站樣像時使用的儲存機制。

public class TradeController : BaseController {
  public void Init() {
    Commands.Open<Trade>.Activated += (args => {
      TradeForm f = OpenTradeForm(args.Trade);
      Delegate tradeUpdated = this.CallOnUiThread(
        (trade) =>  f.TradeUpdated(trade) );
      this.Repository<Trade>.When((t => t.Id == args.Trade.Id))
       .Subscribe(tradeUpdated);
      f.Closed += 
       () => this.Repository<Trade>.Unsubscribe(tradeUpdated);
    });
  }
}

我們的控制站現在使用存放庫訂閱特定交易執行個體上的變更與 UI 所提供的識別項。 拼圖的最後一個部分是只讓我們用戶端端的 WCF 物件使用的相同的儲存機制更新用戶端的網域物件。

效能

之後的所有片段,都放在位置,並重新撰寫的用戶端應用程式的重要部分,介紹支援架構,並將透過我們進行效能測試,我們發現發生仍然發生問題。

大量負載許多物件有已更新在大約在相同的時間時正在處理用戶端大型的通知會造成 UI 變得緩慢。 我們檢測顯示,大通知,長同步處理領域所持有的背景執行緒,在使用者無法執行任何動作,需要控制器邏輯。

我們嘗試最佳化的用戶端程式碼,讓圖形會較小並複製 (Clone),較少時間和一切我們可以把重整網域物件。 我們在用戶端上的任何協助。

這時新進的伺服器開發人員的其中一個支點設定 (有點 hesitantly),建議我們無法將發行一個以上的通知訊息伺服器程式碼變更。 而不是放在一封郵件的所有變更的項目,我們無法做一個實體,每一個訊息,或任何其他,可能甚至使其設定。

並且它做了全世界的所有差異。

自之間處理一個通知] 及 [下一個用戶端上的,背景執行緒 retreated 從同步處理網域,允許此 UI 執行緒取得的中,並執行某些工作,使用者的代表。 花費的大型的通知,是有點長,可以處理其中一個用戶端上一次的訊息是完全可接受。

經驗學習

當我們開始這個專案的開發上時,我們會假設會像其他任何的 Rich-用戶端 / 資料庫系統。 大於任何其他我們會看到我們想架構和技術上面臨兩者挑戰。 低階的執行緒處理問題的兩個伺服器,並在用戶端,瞭解如何偶而連線會影響延展性,] 和 [時避免死結 (Deadlock) 和競爭情形中維護可接受的使用者互動等級-沒有同時按一下只權限的工作,整個動作有許多片段。 所有支援的技術有,所有在如何將它們一起。

在的最後不過,我們監看我們的使用者,我們在全球各地即時共同作業,瞭解它無法任何其他的方式。 系統都依賴提取的使用者的公司排序],] 和 [群組資訊,就不能夠競爭。 軟體 + 服務、 偶爾連線的用戶端和 multicore 的 Revolution 會代表以往更高的產能和獲利性的組織能夠在閏年相符。

我們的利害關係者是對的。 從世界即時使用者介面事件能夠真的是支援協同作業,即時的企業知識工作者的重要。

完成專案之後我是有點擔心的模式和技術,我想挑選不會作為我在我更多的 Line-of-Business 樣式應用程式開發的轉換。 我已經告訴您我驚訝 pleasantly 了。

在多個的個人記事我可以告知您,任何會更好的開發人員的工作,比 Line-of-Business raving 有關您的應用程式的管理員,並開發本身是更有趣。 您不妨試一試。

Udi Dahan 是 The Software Simplist、 MVP 和使用 WCF、 WindowsWF 和奧斯陸 」 連線技術顧問。 他會提供諮詢服務,專精於服務導向、 可調整,和安全性的.NET 架構設計的教育訓練、 mentoring,和高層次架構。 透過他的部落格,可以連絡 Udi: www.UdiDahan.com.