背景
事件有兩種使用方式:一、作為傳統的監聽者模式以達到程序結構的解耦;二、作為消息機制以達到時間和空間上的解耦,如發送到遠程服務器、持久化到隊列等待。今天介紹如何使用“離線事件”處理“長事務”,這就需要把事件當做消息對待。
我理解的長事務是“執行時間長的任務,具體多少沒有標准”,如果希望在一個數據庫事務中完成這些長事務是不現實的,之前我的做法是換成存儲過程以降低事務的執行時間,以后我會采用“離線事件”。
離線事件:事件的一部分是同步執行,另外一部分會被異步的離線的在另外一台機器執行。
簡單示例
下載地址:OfflineEventStudy。
項目結構

- Common:類庫,包含了事件和事件監聽者(同步事件監聽者和離線事件監聽者)。
- Console:離線事件執行程序,定時從隊列取事件並執行離線事件監聽者。
- Web:事件生成程序,發布事件並執行同步事件監聽者。
Common中的代碼
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 using Happy.Event; 8 using Happy.Event.Offline; 9 10 namespace OfflineEventStudy.Common 11 { 12 [Persistable(1)] 13 public sealed class TestEvent : IEvent 14 { 15 public const string FilePath = @"E:\log.txt"; 16 17 public string Info { get; set; } 18 } 19 }
注意:Persistable特性會導致此事件支持離線訂閱。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.IO; 7 using System.Threading; 8 9 using Happy.Event; 10 using Happy.Event.Offline; 11 12 namespace OfflineEventStudy.Common 13 { 14 public sealed class TestEventSubscriber : ISyncEventSubscriber<TestEvent>, IOfflineEventSubscriber<TestEvent> 15 { 16 void ISyncEventSubscriber<TestEvent>.Handle(TestEvent @event) 17 { 18 File.WriteAllText(TestEvent.FilePath, @event.Info + ":任務正在執行中!"); 19 } 20 21 void IOfflineEventSubscriber<TestEvent>.Handle(TestEvent @event) 22 { 23 Thread.Sleep(5000); 24 25 File.WriteAllText(TestEvent.FilePath, @event.Info + ":任務完成!"); 26 } 27 } 28 }
注意:同步接口會在Web中執行,離線接口會在Console中執行。
Console中的代碼
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Threading; 7 8 using Happy.Event; 9 using Happy.Event.Offline; 10 using Happy.Bootstrap; 11 using Happy.Infrastructure.Unity; 12 13 namespace OfflineEventStudy.Console 14 { 15 class Program 16 { 17 static void Main(string[] args) 18 { 19 /****************************************啟動過程配置****************************************/ 20 21 BootstrapService 22 .Current 23 .IntegrateWithUnity() //使用Unity作為Ioc容器。 24 .UseRegisterServiceByConventionPlug() //使用按照約定注冊服務插件,會自動幫你執行注冊。 25 .UseEventSubscriberRegister() //注冊所有的EventListener。 26 .Done() //完成配置。 27 .Start(); //啟動。 28 29 /****************************************啟動過程配置****************************************/ 30 31 var processor = new OfflineEventProcessor(OfflineEventQueueFactory.CreateMSMQOfflineEventQueue("OfflineEventStudy")); 32 33 ThreadPool.QueueUserWorkItem((state) => 34 { 35 processor.Start(); 36 }, null); 37 38 System.Console.WriteLine("正在監聽任務"); 39 System.Console.Read(); 40 } 41 } 42 }
注意:這里會不斷的從隊列取事件並執行。
Web中的代碼
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.Web.UI; 6 using System.Web.UI.WebControls; 7 using System.IO; 8 9 using Happy.Event; 10 using OfflineEventStudy.Common; 11 12 namespace OfflineEventStudy.Web 13 { 14 public partial class Default : System.Web.UI.Page 15 { 16 protected void Page_Load(object sender, EventArgs e) 17 { 18 if (this.IsPostBack) 19 { 20 return; 21 } 22 23 this.Display(); 24 } 25 26 protected void Button1_Click(object sender, EventArgs e) 27 { 28 EventPublisher.Current.Publish(new TestEvent { Info = "測試事件" }); 29 30 this.Display(); 31 } 32 33 protected void Button2_Click(object sender, EventArgs e) 34 { 35 this.Display(); 36 } 37 38 private void Display() 39 { 40 if (!File.Exists(TestEvent.FilePath)) 41 { 42 return; 43 } 44 45 this.Label1.Text = File.ReadAllText(TestEvent.FilePath); 46 } 47 } 48 }
運行效果

備注
因為事件的一部分是離線執行的,所以不能保證實時一致性,關於最終一致性的問題這里不好多說,有興趣的朋友可以找http://www.cnblogs.com/netfocus/聊聊,netfocus最新力作enode完全放棄實時一致性,換來的是高並發。
