我认为《耗时很长的服务器端事件中让客户端得到中间过程信息的合理解决方案》一文中的解决方案不合理,属于技术的误用,满篇的ajax这个词很容易给读者造成误导。
原需求:
B/S结构的系统里,用户点一个按钮系统开始发送上千封邮件,要求把发送信息(发送成功数,失败数,剩余数量...)动态实时的反馈给客户.
原文的技术误用之处:
(1)ajax,pageload 这些ui层的东东渗透到逻辑层里去了
(2)thread的职责太多
并不是网页上的多线程不好搞,而是Web开发搞久了,基本的OO设计能力下降了,或者根本就忽略OO设计了,眼中只有页面。
以原文这个例子而言,你不把它当成Web开发,一切就变得很简单了。
我的解决方案:
首先,得有一个类管理每条信息。
{
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 "等待发送";
}
}
}
![](https://www.cnblogs.com/Images/dot.gif)
}
public class MailMessageHolder : MessageHolder
{
public String Title { get; private set; }
public String Text { get; private set; }
![](https://www.cnblogs.com/Images/dot.gif)
}
然后,得有一个容器管理用户的发送信息
{
protected SortedDictionary<String, IList<TMessageHolder>> Cache { get; set; } // Cache(ReceiverMail, MsgHolders),我的程序主要功能不是群发邮件,所以这样设计
public MessageCache()
{
Cache = new SortedDictionary<string, IList<TMessageHolder>>();
}
public void Add(TMessageHolder msgHolder)
{
![](https://www.cnblogs.com/Images/dot.gif)
}
public IList<TMessageHolder> GetAllMessage()
{
![](https://www.cnblogs.com/Images/dot.gif)
}
public IList<TMessageHolder> GetAllMessageByReceiver(String receiverMail)
{
![](https://www.cnblogs.com/Images/dot.gif)
}
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)
{
![](https://www.cnblogs.com/Images/dot.gif)
}
}
最后,得有线程发送
{
while (true)
{
if (ExitAllDaemonThreads) return;
![](https://www.cnblogs.com/Images/dot.gif)
IList<MailMessageHolder> hl = this.MailMessageCache.FindNeedToSendMessageHolders(MailMessageCache.GetAllMessage());
if (hl == null || hl.Count == 0)
{
lock (SendMailMessageCacheThreadSyncRoot)
{
Monitor.Wait(SendMailMessageCacheThreadSyncRoot);
continue;
}
}
else
{
foreach (MailMessageHolder h in hl)
{
if (ExitAllDaemonThreads) return;
if (h.SendExpired) continue;
if (!h.SendOk)
{
try
{
SendMail(h);
Thread.Sleep(1000);
}
catch (SmtpException se)
{
![](https://www.cnblogs.com/Images/dot.gif)
h.ErrorMessage = se.StatusCode.ToString();
![](https://www.cnblogs.com/Images/dot.gif)
}
}
}
}
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,可以多个用户同时登陆使用,每个用户可以有多任务,而我还想对用户进行区分,比如会员可以爬行快点,一般的慢点,会员爬的层次多,一般的爬的少,采用的就是类似的方案,不过后台线程调度复杂一点(其实也复杂不了多少)。
只提供思路,不提供具体源码。
BTW. 群发可以用来干坏事,也有很多好的应用。