8

我的网站用户执行的某些操作会导致发送电子邮件。发送电子邮件的代码可能会阻塞一段时间,所以我想在他们的 HTTP 请求处理程序的线程中执行此操作。

目前我正在使用类似的东西:

ThreadPool.QueueUserWorkItem(o => {
    try
    {
        email.Send();
    }
    catch (Exception ex)
    {
        _log.Error("Error sending email", ex);
    }
});

在大多数情况下,这是可行的。但是,该网站在可以回收应用程序池的托管环境中运行。

每隔一段时间我都没有收到应该发送的电子邮件,并且我怀疑线程池队列上的这个工作项在应用程序池回收期间被丢弃了。

我怎样才能执行这样的异步操作并保证它会在这种情况下完成?

4

2 回答 2

8

如果您的应用程序以集成模式运行,您可以在主机环境中注册您的邮件调度程序服务。主机将在回收完成之前通知您的服务。主机将调用您的执行IRegisteredObject.Stop恰好 2 次。在第一次通话时,主持人为您提供完成工作的机会。如果达到超时并且您的服务尚未从主机中删除自己,则会进行另一个调用,但这一次只是通知将在有或没有服务同意的情况下进行回收。

这是如何实现 Stop() 方法的示例(未测试):

public class MailDispatchService : IRegisteredObject
{
    private AutoResetEvent _processQueueEvt = new AutoResetEvent();
    private ConcurrentQueue<MailMessage> _queue = new ConcurrentQueue<MailMessage>();
    private Thread _dispatcherThread;
    private volatile bool _enabled = true;

    #region Implementation of IRegisteredObject

    public void Stop(bool immediate)
    {
        if (_dispatcherThread != null && _dispatcherThread.IsAlive)
        {
            // it's not an immediate stop, we can wait for the queue to empty
            if (!immediate)
            {
                // stop accepting new items in the send queue...
                _enabled = false;
                // awake dispatcher thread, so it can quit if the queue is empty
                _processQueueEvt.Set();
                // and wait for a while but not forever.
                _dispatcherThread.Join(TimeSpan.FromSeconds(30));
            }
            else
            {
                // host env will recycle now, nothing to do...
                _dispatcherThread.Abort();
            }
        }
        // remove the service from host
        HostingEnvironment.UnregisterObject(this);
    }

    #endregion

    public void Start()
    {
        _dispatcherThread = new Thread(ProcessQueue);
        _dispatcherThread.Start();
    }

    private void ProcessQueue()
    {
        while (_enabled)
        {
            _processQueueEvt.WaitOne();
            MailMessage message;
            while (_queue.TryDequeue(out message)) { /* send mail ...*/}
        }
    }

    public void DispatchEmail(MailMessage message)
    {
        if (!_enabled) throw new Exception("....");
        _queue.Enqueue(message);
        _processQueueEvt.Set();
    }
}

启动服务并在主机上注册。

var mailService = new MailDispatchService();
System.Web.Hosting.HostingEnvironment.RegisterObject(mailService);
mailService.Start();

var message = new MailMessage();
mailService.DispatchEmail(message);   
于 2012-08-07T14:14:21.670 回答
2

这里最好的选择是使用持久消息队列或服务总线。您的应用程序将电子邮件请求写入队列,队列处理程序(可能是您的网络应用程序)读取队列并处理消息。如果事情死了,持久性角度就会开始——消息会一直存在,直到它们可以被处理。

于 2012-08-07T14:27:10.220 回答