私の提案は、Quartz.NETを使用する単純なアプリケーションを作成することです。
2 つのジョブを作成します。
- まず、1 日に 1 回起動し、その日に計画されたデータベースからすべての待機中の通知時間を読み取り、それらに基づいていくつかのトリガーを作成します。
- 次に、そのようなトリガーに登録され (最初のジョブによって準備されます)、通知が送信されます。
そのうえ、
このような目的のために 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);
}
}
データベースから必要な情報を収集し、通知をスケジュールするための最初のジョブ(2 番目のジョブ):
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 */ }
}
}
2 番目のジョブ、メール送信用:
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);
}
}
この特定の例の目的のためだけに、データベースのモックを作成します。
internal class DbMock
{
public static IDictionary<string, string> ScheduleMap =
new Dictionary<string, string>
{
{"00:01", "foo@gmail.com"},
{"00:02", "bar@yahoo.com"}
};
}
アプリケーションの主なエントリ:
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);
}
}
出力:

serviceを使用する場合は、このメイン ロジックを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 */ }
}
その場合は、スレッドからのエラーをキャッチする WatchThread などのラッパーにロジックをカプセル化します。
private void WatchThread(object pointer)
{
try
{
((Delegate) pointer).DynamicInvoke();
}
catch (Exception e) { /* log error and stop service */ }
}