天天看點

耗時很長的伺服器端事件中讓用戶端得到中間過程資訊的更合理的解決方案

原需求:

B/S結構的系統裡,使用者點一個按鈕系統開始發送上千封郵件,要求把發送資訊(發送成功數,失敗數,剩餘數量...)動态實時的回報給客戶.

原文的技術誤用之處:

(1)ajax,pageload 這些ui層的東東滲透到邏輯層裡去了

(2)thread的職責太多

并不是網頁上的多線程不好搞,而是Web開發搞久了,基本的OO設計能力下降了,或者根本就忽略OO設計了,眼中隻有頁面。

以原文這個例子而言,你不把它當成Web開發,一切就變得很簡單了。

我的解決方案:

首先,得有一個類管理每條資訊。

耗時很長的伺服器端事件中讓用戶端得到中間過程資訊的更合理的解決方案

    public abstract class MessageHolder

    {

        public DateTime SendTime { get; set; }

        public String ReceiverMail { get { return Receiver.Mail; } }

        public String ErrorMessage { get; set; }

        public Int32 SendCount { get; set; }

        public Boolean SendExpired { get { return SendCount > 5; } }

        public Boolean SendOk { get; private set; }

        public Guid Id { get; private set; }

        public String SendResult {

            get {

                if (SendOk) return "發送成功";

                else

                {

                    if (SendExpired) return "發送失敗";

                    else return "等待發送";

                }

            }

        }

耗時很長的伺服器端事件中讓用戶端得到中間過程資訊的更合理的解決方案

    }

    public class MailMessageHolder : MessageHolder

        public String Title { get; private set; }

        public String Text { get; private set; }

耗時很長的伺服器端事件中讓用戶端得到中間過程資訊的更合理的解決方案
耗時很長的伺服器端事件中讓用戶端得到中間過程資訊的更合理的解決方案

然後,得有一個容器管理使用者的發送資訊

耗時很長的伺服器端事件中讓用戶端得到中間過程資訊的更合理的解決方案

    public class MessageCache<TMessageHolder> where TMessageHolder : MessageHolder

        protected SortedDictionary<String, IList<TMessageHolder>> Cache { get; set; }    // Cache(ReceiverMail, MsgHolders),我的程式主要功能不是群發郵件,是以這樣設計

        public MessageCache()

        {

            Cache = new SortedDictionary<string, IList<TMessageHolder>>();

        public void Add(TMessageHolder msgHolder)

耗時很長的伺服器端事件中讓用戶端得到中間過程資訊的更合理的解決方案

        public IList<TMessageHolder> GetAllMessage()

耗時很長的伺服器端事件中讓用戶端得到中間過程資訊的更合理的解決方案

        public IList<TMessageHolder> GetAllMessageByReceiver(String receiverMail)

耗時很長的伺服器端事件中讓用戶端得到中間過程資訊的更合理的解決方案

        public IList<TMessageHolder> FindNeedToSendMessageHolders(IList<TMessageHolder> holderList)

            if (holderList == null) return null;

            IList<TMessageHolder> hl = new List<TMessageHolder>();

            foreach (TMessageHolder h in holderList)

            {

                if ((!h.SendExpired) && (!h.SendOk)) hl.Add(h);

            return hl;

        public IList<TMessageHolder> FindSendOkMessageHolders(IList<TMessageHolder> holderList)

耗時很長的伺服器端事件中讓用戶端得到中間過程資訊的更合理的解決方案
耗時很長的伺服器端事件中讓用戶端得到中間過程資訊的更合理的解決方案

最後,得有線程發送

耗時很長的伺服器端事件中讓用戶端得到中間過程資訊的更合理的解決方案

        public void ThreadStart_SendMailMessageCache()

            while (true)

                if (ExitAllDaemonThreads) return;

耗時很長的伺服器端事件中讓用戶端得到中間過程資訊的更合理的解決方案

                IList<MailMessageHolder> hl = this.MailMessageCache.FindNeedToSendMessageHolders(MailMessageCache.GetAllMessage());

                if (hl == null || hl.Count == 0)

                    lock (SendMailMessageCacheThreadSyncRoot)

                    {

                        Monitor.Wait(SendMailMessageCacheThreadSyncRoot);

                        continue;

                    }

                    foreach (MailMessageHolder h in hl)

                        if (ExitAllDaemonThreads) return;

                        if (h.SendExpired) continue;

                        if (!h.SendOk)

                        {

                            try

                            {

                                SendMail(h);

                                Thread.Sleep(1000);

                            }

                            catch (SmtpException se)

耗時很長的伺服器端事件中讓用戶端得到中間過程資訊的更合理的解決方案

                                h.ErrorMessage = se.StatusCode.ToString();

耗時很長的伺服器端事件中讓用戶端得到中間過程資訊的更合理的解決方案

                        }

                Thread.Sleep(30000);

耗時很長的伺服器端事件中讓用戶端得到中間過程資訊的更合理的解決方案

當有新發送任務時先将任務批量添加到MessageCache,然後Monitor.PulseAll(SendMailMessageCacheThreadSyncRoot)一下。如果覺得背景線程太多不好的話,也可以沒有任務時中止線程,有任務時再啟動。

這種解決方案的好處非常明顯:

(1)無論是B/S程式還是C/S程式還是控制台程式都使用,換個UI非常簡單

(2)檢視發送進度:

 MessageCache<TMessageHolder>.GetAllMessage(),搞個GridView顯示下就行了,可以看具體的發送資訊。也可以計算統計資訊,都挺簡單的。

(3)具備發送失敗自動重發功能

(4)使用者可以同時送出多個發送任務

(5)用個持久化方案就可以實作斷點重發。B/S用資料庫,C/S用檔案或資料庫

(6)與Session無關

(7)背景非常柔性。你可以寫一個線程處理多個MessageCache,也可以多個線程處理多個MessageCache,也可以多個線程處理一個MessageCache,也可以采用異步方式處理,也可以多線程+異步,也可以用協程排程,也可以用event-dispatch-scheduler方式處理,也可以存在資料庫讓幾台機器一起發,也可以像TCP協定那樣加入擁塞控制機制。總之根據需求與伺服器的負載情況來。

這種解決方案适用面很廣,比如,我寫的B/S式的Spider,可以多個使用者同時登陸使用,每個使用者可以有多任務,而我還想對使用者進行區分,比如會員可以爬行快點,一般的慢點,會員爬的層次多,一般的爬的少,采用的就是類似的方案,不過背景線程排程複雜一點(其實也複雜不了多少)。

隻提供思路,不提供具體源碼。

 本文轉自xiaotie部落格園部落格,原文連結http://www.cnblogs.com/xiaotie/archive/2008/08/17/1269902.html如需轉載請聯系原作者

xiaotie 集異璧實驗室(GEBLAB)