SlidingInvisibilityTimeout上的篝火澄清

问题描述 投票:0回答:1

hangfire文档状态:

原始SQL Server作业存储实现的主要缺点之一–它使用轮询技术来获取新作业。从Hangfire 1.7.0开始,设置了SlidingInvisibilityTimeout选项后,可以将TimeSpan.Zero用作轮询间隔。

并且我正在按照建议使用这些SqlServerStorageOptions:

var options = new SqlServerStorageOptions
{
    SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
    QueuePollInterval = TimeSpan.Zero
};

它在任何地方都没有说明SlidingInvisibilityTimeout的实际含义,有人可以澄清吗?我的情况是,我每天早晨大约要发送1000封电子邮件,而且我一直在突破每分钟30条消息的Office365限制,并遭到拒绝,因此我正在使用Hangfire在单个workerthread中将它们排队,并添加了每个任务结束时有2秒的Thread.Sleep。这工作得很好,但是由于hangfire(as reported here)而导致CPU使用率增加了大约20%,并且在服务器繁忙时导致频繁的超时。

我试图实现的行为是:

  1. 每项工作结束后,立即检查是否还有另一项并执行下一项任务。
  2. 如果队列中没有作业,请在5分钟内返回并在此之前不要触摸SQL Server。

感谢您的协助。

hangfire
1个回答
0
投票

最后,我编写了一种轻量级的替代方法,它使用while循环中的线程来监视文件夹中是否包含带有电子邮件参数的序列化对象的文本文件。它几乎没有开销,并且符合office365节流策略。如果它对其他人有用,请在此处发布,它是为asp.net构建的,应该易于适应其他情况。

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Web.Hosting;

namespace Whatever
{
    public class EmailerThread
    {        
        public delegate void Worker();
        private static Thread worker;   // one worker thread for this application  // https://stackoverflow.com/questions/1824933/right-way-to-create-thread-in-asp-net-web-application
        public static string emailFolder;
        public static int ScanIntervalMS = 2000;     // office365 allows for 30 messages in a 60 second window, a 2 second delay plus the processing time required to connect & send each message should safely avoid the throttling restrictions.

        /// <summary>
        /// Must be invoked from Application_Start to ensure the thread is always running, if the applicationpool recycles etc
        /// </summary>
        public static void Init()
        {
            // create the folder used to store serialized files for each email to be sent
            emailFolder = Path.Combine(HostingEnvironment.ApplicationPhysicalPath, "App_Data", "_EmailOutbox");
            Directory.CreateDirectory(emailFolder);

            worker = new Thread(new ThreadStart(new Worker(ScanForEmails)));
            worker.Start();
        }

        /// <summary>
        /// Serialize an object containing all the email parameters to a text file
        /// Call this object 
        /// </summary>
        /// <param name="e"></param>
        public static void QueueEmail(EmailParametersContainer e)
        {
            string filename = Guid.NewGuid().ToString() + ".txt";
            File.WriteAllText(Path.Combine(emailFolder, filename), JsonConvert.SerializeObject(e));
        }

        public static void ScanForEmails()
        {
            var client = new System.Net.Mail.SmtpClient(Settings.SmtpServer, 587);
            client.EnableSsl = true;
            client.UseDefaultCredentials = false;
            client.DeliveryMethod = SmtpDeliveryMethod.Network;
            client.Credentials = new System.Net.NetworkCredential(Settings.smtpUser, Settings.smtpPass);
            client.Timeout = 5 * 60 * 1000;    // 5 minutes

            // infinite loop to keep scanning for files
            while (true)
            {
                // take the oldest file in the folder and process it for sending
                var nextFile = new DirectoryInfo(emailFolder).GetFiles("*.txt", SearchOption.TopDirectoryOnly).OrderBy(z => z.CreationTime).FirstOrDefault();
                if (nextFile != null)
                {
                    // deserialize the file
                    EmailParametersContainer e = JsonConvert.DeserializeObject<EmailParametersContainer>(File.ReadAllText(nextFile.FullName));
                    if (e != null)
                    {
                        try
                        {
                            MailMessage msg = new MailMessage();
                            AddEmailRecipients(msg, e.To, e.CC, e.BCC);
                            msg.From = new MailAddress(smtpUser);
                            msg.Subject = e.Subject;
                            msg.IsBodyHtml = e.HtmlFormat;
                            msg.Body = e.MessageText;

                            if (e.FilePaths != null && e.FilePaths.Count > 0)
                                foreach (string file in e.FilePaths)
                                    if (!String.IsNullOrEmpty(file) && File.Exists(file))
                                        msg.Attachments.Add(new Attachment(file));

                            client.Send(msg);
                            msg.Dispose();

                            // delete the text file now that the job has successfully completed
                            nextFile.Delete();
                        }
                        catch (Exception ex)
                        {
                            // Log the error however suits...

                            // rename the .txt file to a .fail file so that it stays in the folder but will not keep trying to send a problematic email (e.g. bad recipients or attachment size rejected)
                            nextFile.MoveTo(nextFile.FullName.Replace(".txt", ".fail"));
                        }
                    }
                }
                Thread.Sleep(ScanIntervalMS);   // wait for the required time before looking for another job
            }            
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="Recipients">Separated by ; or , or \n or space</param>
        public static void AddEmailRecipients(MailMessage msg, string To, string CC, string BCC)
        {
            string[] list;
            if (!String.IsNullOrEmpty(To))
            {
                list = To.Split(";, \n".ToCharArray());
                foreach (string email in list)
                    if (email.Trim() != "" && ValidateEmail(email.Trim()))
                        msg.To.Add(new MailAddress(email.Trim()));
            }
            if (!String.IsNullOrEmpty(CC))
            {
                list = CC.Split(";, \n".ToCharArray());
                foreach (string email in list)
                    if (email.Trim() != "" && ValidateEmail(email.Trim()))
                        msg.CC.Add(new MailAddress(email.Trim()));
            }
            if (!String.IsNullOrEmpty(BCC))
            {
                list = BCC.Split(";, \n".ToCharArray());
                foreach (string email in list)
                    if (email.Trim() != "" && ValidateEmail(email.Trim()))
                        msg.Bcc.Add(new MailAddress(email.Trim()));
            }
        }


        public static bool ValidateEmail(string email)
        {
            if (email.Contains(" ")) { return false; }

            try
            {
                // rely on the .Net framework to validate the email address, rather than attempting some crazy regex
                var m = new MailAddress(email);
                return true;                
            }
            catch
            {
                return false;
            }
        }
    }

    public class EmailParametersContainer
    {
        public string To { get; set; }
        public string Cc { get; set; }
        public string Bcc { get; set; }
        public string Subject { get; set; }
        public string MessageText { get; set; }
        public List<string> FilePaths { get; set; }
        public bool HtmlFormat { get; set; }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.