需求:
B/S结构的系统里,用户点一个按钮系统开始发送上千封邮件,要求把发送信息(发送成功数,失败数,剩余数量...)动态实时的反馈给客户.
分析和实施过程当中遇到的问题:
一:最低级的问题
由于客户催的紧,发邮件的核心代码写好后就开始给他使用了,当时系统还没上AJAX.
最初的问题是一点按钮过不了几分钟就页面超时(要想页面不超时必须定时给页面输出一些东西),
搞定了页面超时的问题然后就是服务器IIS超时
设置了IIS超时时间就又SQL连接超时
最后寻思这样下去总不是个办法
决定上ajax(正如大家想的一样)
二:开始想到了ajax
上ajax又碰到一个问题
ICallbackEventHandler只提供了两个方法,
一个是被客户端触发的服务器端事件,
一个是服务器端事件完成后的反馈事件
两个事件是顺序发生的,
我如果在一个事件中执行发送邮件的过程,
我就不能在这个事件中把中间过程的信息反馈给客户
我的两个需求必须同时进行!
我甚至想到:当用户点按钮的时候同时触发ajax事件和postback事件,
多么愚蠢的idea啊(回发了还怎能异步刷新)
最后:多方求助+苦思冥想最后得出两种解决方案
1.通过ajax每次发送一定数量的邮件
用javascript循环把邮件地址发送给服务器端(以ajax方式),
每循环一次给服务器端10条信息,
服务器端把这10个邮件发完之后,反馈客户端一次
客户端通过js更新提示信息(已经发完十封了)
然后进入下一次ajax循环
2.ajax调用服务器端事件,在服务器端事件里使用多线程技术
当用户点按钮触发了ajax服务器端事件后,
在这个事件里我建立了两个线程
一个线程开始发送邮件,另一个线程负责返回信息
因为要实时的返回信息,
所以这个ajax事件肯定是定时调用的.(我是每4秒获取一下服务器端的信息)
服务器端事件开始执行,
先判断发邮件的线程是否已经开始了,
如果没开始就建立发邮件的线程,
并执行线程
如果开始了(那么说明这个调用肯定不是第一次调用)
就执行反馈信息的代码
两种方案都是可行的,我最终选择了第二种
想法随好,在实施过程中又碰到了N多问题
三.实施过程中的问题
1.假如在发送过程中用户出现了断网,或者不小心关闭了页面,我怎么让他下次登陆的时候继续发送.
在这里我想到了消息队列,事务等,最终的解决方案是
开始发邮件前先把所有待发的邮件存储到数据库的一个临时表里去,
发一封删除一条记录,
pagelodad里检测该表是否有记录,
如果有记录就直接发送该表里的邮件(也就是尚未完成的邮件)
这里可以用Page.ClientScript.RegisterStartupScript注册一个客户端事件调用我们的ajax函数
2.线程的参数问题
发送邮件的线程方法是肯定需要参数的,然而new Thread(new ThreadStart());创建线程的又不允许给线程传参数,
这个问题没有困绕我很久,因为网上有很多解决方案,比如建立一些public的变量或者属性
我用的是另外一种办法,先建立一个对象,然后给这个对象的属性负值(这就是我的参数啦)
然后创建线程的时候线程的方法是这个对象的一个方法sendmail_thread = new Thread(new ThreadStart(sendobj.sendmail_xuan));
3.线程开始状态判断的误区
线程有一个ThreadState属性,我不建议用这个属性的IsAlive判断线程是否开始.
因为代码执行到sendmail_thread = new Thread(new ThreadStart(sendobj.sendmail_xuan));这句后,
线程并不一定处于IsAlive状态,因为他要等服务器的CPU给他分配时间片(具体的我就不说了)
我是用session判断的
4.还是线程的问题
当用户执行了操作,有可能发送邮件的线程还没有开始,而ajax已经去取返回信息了.
(如果计算发送成功率,有可能造成除以0的错误)
或者邮件发送线程已经完成了操作,但ajax还一直在那取后端的反馈信息
(如果反馈发送消耗时间,有可能时间会一直增长)
人们都说网页上的多线程不好搞(每个访问就有可能造成一个线程)
果然如此啊,我这里还没考虑高并发的问题,
然而上面说的那两个问题都不是硬伤,
想想办法还是可以"掩饰"过去的
文章就写到这,我没有公布原代码,只是写了一些思想和解决方法
实在是我这个案例有点偏了.大家如果一定要原代码,那么就在此文章下留言吧
如果要的人超过10个我就写这个文章的续
另:系统开发过程中得到了Jeffrey Zhao joseph.zhu(asp.net第一步的作者) 南洋 的帮助 在此表示感谢
8.19日为了阅读方便,我对文章做了一些修改,主要内容未变