C#console app在预定时间发送电子邮件

Cav*_*rob 15 c# scheduled-tasks console-application

我有一个在Windows Server 2003上运行的C#控制台应用程序,其目的是读取一个名为Notifications的表和一个名为"NotifyDateTime"的字段,并在达到该时间时发送一封电子邮件.我通过任务计划程序安排它按小时运行,检查NotifyDateTime是否在该小时内,然后发送通知.

这似乎是因为我在数据库中有通知日期/时间应该有一个比每小时重新运行这个东西更好的方法.

是否有一个轻量级的进程/控制台应用程序,我可以在服务器上运行,从表中读取当天的通知,并在它们到期时准确发布它们?

我认为服务,但这似乎有点矫枉过正.

jwa*_*zko 26

我的建议是编写简单的应用程序,它使用Quartz.NET.

创造2个职位:

  • 首先,每天触发一次,从当天计划的数据库中读取所有等待通知的时间,根据它们创建一些触发器.
  • 第二,注册此类触发器(由第一份工作准备),发送您的通知.

更重要的是,

我强烈建议你为此目的创建Windows服务,只是不要让寂寞的控制台应用程序不断运行.在同一帐户下有权访问服务器的人可能会意外终止它.更重要的是,如果服务器将重新启动,您必须记住手动重新打开此类应用程序,同时可以将服务配置为自动启动.

如果您正在使用Web应用程序,则可以始终在IIS应用程序池进程中托管此逻辑,尽管这是一个坏主意.这是因为默认情况下会定期重新启动此类进程,因此您应该更改其默认配置,以确保在未使用应用程序时它仍在半夜工作.除非您的计划任务将被终止.

更新(代码示例):

Manager类,用于调度和取消调度作业的内部逻辑.出于安全原因实施为单身人士:

internal class ScheduleManager
{
    private static readonly ScheduleManager _instance = new ScheduleManager();
    private readonly IScheduler _scheduler;

    private ScheduleManager()
    {
        var properties = new NameValueCollection();
        properties["quartz.scheduler.instanceName"] = "notifier";
        properties["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz";
        properties["quartz.threadPool.threadCount"] = "5";
        properties["quartz.threadPool.threadPriority"] = "Normal";

        var sf = new StdSchedulerFactory(properties);
        _scheduler = sf.GetScheduler();
        _scheduler.Start();
    }

    public static ScheduleManager Instance
    {
        get { return _instance; }
    }

    public void Schedule(IJobDetail job, ITrigger trigger)
    {
        _scheduler.ScheduleJob(job, trigger);
    }

    public void Unschedule(TriggerKey key)
    {
        _scheduler.UnscheduleJob(key);
    }
}
Run Code Online (Sandbox Code Playgroud)

第一项工作,用于从数据库收集所需信息和安排通知(第二项工作):

internal class Setup : IJob
{
    public void Execute(IJobExecutionContext context)
    {
        try
        {                
            foreach (var kvp in DbMock.ScheduleMap)
            {
                var email = kvp.Value;
                var notify = new JobDetailImpl(email, "emailgroup", typeof(Notify))
                    {
                        JobDataMap = new JobDataMap {{"email", email}}
                    };
                var time = new DateTimeOffset(DateTime.Parse(kvp.Key).ToUniversalTime());
                var trigger = new SimpleTriggerImpl(email, "emailtriggergroup", time);
                ScheduleManager.Instance.Schedule(notify, trigger);
            }
            Console.WriteLine("{0}: all jobs scheduled for today", DateTime.Now);
        }
        catch (Exception e) { /* log error */ }           
    }
}
Run Code Online (Sandbox Code Playgroud)

第二份工作,用于发送电子邮件:

internal class Notify: IJob
{
    public void Execute(IJobExecutionContext context)
    {
        try
        {
            var email = context.MergedJobDataMap.GetString("email");
            SendEmail(email);
            ScheduleManager.Instance.Unschedule(new TriggerKey(email));
        }
        catch (Exception e) { /* log error */ }
    }

    private void SendEmail(string email)
    {
        Console.WriteLine("{0}: sending email to {1}...", DateTime.Now, email);
    }
}
Run Code Online (Sandbox Code Playgroud)

数据库模拟,仅用于此特定示例的目的:

internal class DbMock
{
    public static IDictionary<string, string> ScheduleMap = 
        new Dictionary<string, string>
        {
            {"00:01", "foo@gmail.com"},
            {"00:02", "bar@yahoo.com"}
        };
}
Run Code Online (Sandbox Code Playgroud)

申请主要条目:

public class Program
{
    public static void Main()
    {
        FireStarter.Execute();
    }
}

public class FireStarter
{
    public static void Execute()
    {
        var setup = new JobDetailImpl("setup", "setupgroup", typeof(Setup));
        var midnight = new CronTriggerImpl("setuptrigger", "setuptriggergroup", 
                                           "setup", "setupgroup",
                                           DateTime.UtcNow, null, "0 0 0 * * ?");
        ScheduleManager.Instance.Schedule(setup, midnight);
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

在此输入图像描述

如果您要使用服务,只需将此主逻辑放入OnStart方法(我建议在单独的线程中启动实际逻辑而不是等待服务启动,同样避免可能的超时 - 不在此特定示例中显然,但总的来说):

protected override void OnStart(string[] args)
{
    try
    {
        var thread = new Thread(x => WatchThread(new ThreadStart(FireStarter.Execute)));
        thread.Start();
    }
    catch (Exception e) { /* log error */ }            
}
Run Code Online (Sandbox Code Playgroud)

如果是这样,将逻辑封装在一些包装器中,例如WatchThread,它将捕获来自线程的任何错误:

private void WatchThread(object pointer)
{
    try
    {
        ((Delegate) pointer).DynamicInvoke();
    }
    catch (Exception e) { /* log error and stop service */ }
}
Run Code Online (Sandbox Code Playgroud)


Jas*_*irk 1

计划任务可以安排在特定时间仅运行一次(而不是每小时、每天等),因此一种选择是在数据库中的特定字段发生更改时创建计划任务。

您没有提及您使用哪个数据库,但有些数据库支持触发器的概念,例如在 SQL 中:http ://technet.microsoft.com/en-us/library/ms189799.aspx