Dot-Net

執行保證在 IIS 網站內執行的非同步操作,即使應用程序池被回收

  • January 1, 2020

我的網站使用者執行的某些操作會導致發送電子郵件。發送電子郵件的程式碼可能會阻塞一段時間,所以我想在他們的 HTTP 請求處理程序的執行緒中執行此操作。

目前我正在使用類似的東西:

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

在大多數情況下,這是可行的。但是,該網站在可以回收應用程序池的託管環境中執行。

每隔一段時間我都沒有收到應該發送的電子郵件,並且我懷疑執行緒池隊列上的這個工作項在應用程序池回收期間被丟棄了。

我怎樣才能執行這樣的非同步操作並保證它會在這種情況下完成?

如果您的應用程序以集成模式執行,您可以在主機環境中註冊您的郵件調度程序服務。主機將在回收完成*之前通知您的服務。*主機將呼叫您的執行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);   

引用自:https://stackoverflow.com/questions/11844034