.NETで1時間ごと(または1時間ごとに特定の時間間隔)にイベントを発生させるにはどうすればよいですか?
-
08-07-2019 - |
質問
システムトレイで実行され、1時間ごとにWebサイトをクロールする小さなWebクローラーで作業しています。
.NETで1時間ごとまたは他の間隔でイベントを発生させて、タスクを実行する最良の方法は何ですか。たとえば、時間に基づいて20分ごとにイベントを実行します。イベントは次の場所で発生します:
00:20
00:40
01:00
01:20
01:40
など。これを行うための最良の方法は、スレッドにループを作成することです。このループは、指定された間隔で時刻が割り切れるかどうかを常にチェックし、時刻に達するとコールバックイベントを発生させます。もっと良い方法がなければならないと感じています。
Timer
を使用しますが、" schedule"の後に続くものが好きです。それはそれらの線に沿って時間または何かで実行されます。
Windowsタスクスケジューラでアプリケーションを設定しなくてもこれは可能ですか?
更新:
タイマーの時間間隔を計算するアルゴリズムを追加しています。このメソッドには" minute
"がかかります。パラメータ。これは、タイマーがティックをトリガーする時間です。たとえば、" minute
"パラメータが20の場合、タイマーは上記のタイムテーブルの間隔でティックします。
int CalculateTimerInterval(int minute)
{
if (minute <= 0)
minute = 60;
DateTime now = DateTime.Now;
DateTime future = now.AddMinutes((minute - (now.Minute % minute))).AddSeconds(now.Second * -1).AddMilliseconds(now.Millisecond * -1);
TimeSpan interval = future - now;
return (int)interval.TotalMilliseconds;
}
このコードは次のように使用されます:
static System.Windows.Forms.Timer t;
const int CHECK_INTERVAL = 20;
static void Main()
{
t = new System.Windows.Forms.Timer();
t.Interval = CalculateTimerInterval(CHECK_INTERVAL);
t.Tick += new EventHandler(t_Tick);
t.Start();
}
static void t_Tick(object sender, EventArgs e)
{
t.Interval = CalculateTimerInterval(CHECK_INTERVAL);
}
解決
System.Timers.Timer 。 1日の特定の時間に実行したい場合は、次回までの時間を把握し、それを間隔として設定する必要があります。
これは単なる基本的な考え方です。必要な精度に応じて、さらに多くのことができます。
int minutes = DateTime.Now.Minute;
int adjust = 10 - (minutes % 10);
timer.Interval = adjust * 60 * 1000;
他のヒント
Quartz.net http://quartznet.sourceforge.net/
これは、スレッドタイミングと非同期呼び出しを使用した軽量システムの例です。
欠点があることはわかっていますが、長時間実行されるプロセス(スケジュールされたバックエンドサービスなど)を開始するときに、タイマーの代わりにこれを使用するのが好きです。タイマースレッドでインラインで実行されるため、元の呼び出しが終了する前に再び開始されることを心配する必要はありません。これは、日付時刻の配列をトリガー時刻として使用するように、またはさらに機能を追加するために、かなり拡張することができます。皆さんの中にはもっと良い方法を知っている人もいると思います。
public Form1()
{
InitializeComponent();
//some fake data, obviously you would have your own.
DateTime someStart = DateTime.Now.AddMinutes(1);
TimeSpan someInterval = TimeSpan.FromMinutes(2);
//sample call
StartTimer(someStart,someInterval,doSomething);
}
//just a fake function to call
private bool doSomething()
{
DialogResult keepGoing = MessageBox.Show("Hey, I did something! Keep Going?","Something!",MessageBoxButtons.YesNo);
return (keepGoing == DialogResult.Yes);
}
//The following is the actual guts.. and can be transplanted to an actual class.
private delegate void voidFunc<P1,P2,P3>(P1 p1,P2 p2,P3 p3);
public void StartTimer(DateTime startTime, TimeSpan interval, Func<bool> action)
{
voidFunc<DateTime,TimeSpan,Func<bool>> Timer = TimedThread;
Timer.BeginInvoke(startTime,interval,action,null,null);
}
private void TimedThread(DateTime startTime, TimeSpan interval, Func<bool> action)
{
bool keepRunning = true;
DateTime NextExecute = startTime;
while(keepRunning)
{
if (DateTime.Now > NextExecute)
{
keepRunning = action.Invoke();
NextExecute = NextExecute.Add(interval);
}
//could parameterize resolution.
Thread.Sleep(1000);
}
}
このためのもう1つの戦略は、プロセスが実行された最終時刻を記録し、その時刻から目的の間隔が経過したかどうかを判断することです。この戦略では、経過時間が望ましい間隔と等しいか、それよりも大きい場合に発生するようにイベントをコーディングします。このようにして、何らかの理由でコンピューターがダウンした場合に長い間隔(たとえば、1日に1回)を逃す可能性があるインスタンスを処理できます。
したがって、たとえば:
- lastRunDateTime = 2009年5月2日午後8時
- 24時間ごとにプロセスを実行したい
- タイマーイベントで、プロセスが最後に実行されてから24時間以上経過しているかどうかを確認します。
- はいの場合、プロセスを実行し、必要な間隔を追加してlastRunDateTimeを更新します(この場合は24時間ですが、必要なものは何でも)
明らかに、システムがダウンした後にこれを回復するには、lastRunDateTimeをファイルまたはデータベースのどこかに保存して、プログラムが回復時に中断した場所から再開できるようにする必要があります。
System.Windows.Forms.Timer(またはSystem.Timers.Timer)
しかし、タイマーを使用したくないと言うようになったので、別のスレッドで軽量の待機プロセスを実行するか(時間をチェックし、数秒スリープし、再び時間をチェックします...)、または特定のスケジュールされた時間または間隔でのイベント(軽量待機プロセスを使用)
私の目標は、毎晩03:00頃にインポートを実行することです。
System.Timers.Timerを使用した私のアプローチです:
private Timer _timer;
private Int32 _hours = 0;
private Int32 _runAt = 3;
protected override void OnStart(string[] args)
{
_hours = (24 - (DateTime.Now.Hour + 1)) + _runAt;
_timer = new Timer();
_timer.Interval = _hours * 60 * 60 * 1000;
_timer.Elapsed += new ElapsedEventHandler(Tick);
_timer.Start();
}
void Tick(object sender, ElapsedEventArgs e)
{
if (_hours != 24)
{
_hours = 24;
_timer.Interval = _hours * 60 * 60 * 1000;
}
RunImport();
}