X分ごとにスレッドをディスパッチする必要があるC#プログラムがありますただし、以前にディスパッチされたスレッドが(X分から)前に現在実行されていない場合のみ。
単純な古いTimer
だけでは機能しません(以前にディスパッチされたプロセスがまだ終了しているかどうかに関係なく、X分ごとにイベントをディスパッチするため)。
ディスパッチされるプロセスは、タスクを実行するのにかかる時間によって大きく異なります。場合によっては1秒かかることもあれば、数時間かかることもあります。プロセスが最後に開始されたときからまだ処理中の場合、プロセスを再び開始したくありません。
誰でも動作するC#サンプルコードを提供できますか?
私の意見では、この状況に行く方法は_System.ComponentModel.BackgroundWorker
_クラスを使用し、新しいスレッドをディスパッチする(またはしない)たびにIsBusy
プロパティをチェックするだけです。コードは非常に単純です。以下に例を示します。
_class MyClass
{
private BackgroundWorker worker;
public MyClass()
{
worker = new BackgroundWorker();
worker.DoWork += worker_DoWork;
Timer timer = new Timer(1000);
timer.Elapsed += timer_Elapsed;
timer.Start();
}
void timer_Elapsed(object sender, ElapsedEventArgs e)
{
if(!worker.IsBusy)
worker.RunWorkerAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
//whatever You want the background thread to do...
}
}
_
この例では_System.Timers.Timer
_を使用しましたが、他のタイマーでも動作するはずです。 BackgroundWorker
クラスは進捗レポートとキャンセルもサポートし、ディスパッチスレッドとの通信のイベント駆動モデルを使用するため、揮発性変数などを心配する必要はありません...
[〜#〜] edit [〜#〜]
キャンセルと進行状況のレポートを含む、より複雑な例を次に示します。
_class MyClass
{
private BackgroundWorker worker;
public MyClass()
{
worker = new BackgroundWorker()
{
WorkerSupportsCancellation = true,
WorkerReportsProgress = true
};
worker.DoWork += worker_DoWork;
worker.ProgressChanged += worker_ProgressChanged;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
Timer timer = new Timer(1000);
timer.Elapsed += timer_Elapsed;
timer.Start();
}
void timer_Elapsed(object sender, ElapsedEventArgs e)
{
if(!worker.IsBusy)
worker.RunWorkerAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker w = (BackgroundWorker)sender;
while(/*condition*/)
{
//check if cancellation was requested
if(w.CancellationPending)
{
//take any necessary action upon cancelling (rollback, etc.)
//notify the RunWorkerCompleted event handler
//that the operation was cancelled
e.Cancel = true;
return;
}
//report progress; this method has an overload which can also take
//custom object (usually representing state) as an argument
w.ReportProgress(/*percentage*/);
//do whatever You want the background thread to do...
}
}
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//display the progress using e.ProgressPercentage and/or e.UserState
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if(e.Cancelled)
{
//do something
}
else
{
//do something else
}
}
}
_
その後、さらなる実行をキャンセルするには、単にworker.CancelAsync()
を呼び出します。これは完全にユーザーが処理するキャンセルメカニズムであることに注意してください(スレッドのアボートなど、すぐに使用できるものはサポートしていません)。
揮発性boolを維持するだけで、要求を達成できます。
private volatile bool _executing;
private void TimerElapsed(object state)
{
if (_executing)
return;
_executing = true;
try
{
// do the real work here
}
catch (Exception e)
{
// handle your error
}
finally
{
_executing = false;
}
}
経過したコールバックでタイマーを無効または有効にできます。
public void TimerElapsed(object sender, EventArgs e)
{
_timer.Stop();
//Do Work
_timer.Start();
}
データ/メソッドを処理する前にSystem.Threading.Timer
を使用し、Timeout
をInfinite
に設定するだけで、完了したらTimer
の準備ができます次の呼び出し。
private System.Threading.Timer _timerThread;
private int _period = 2000;
public MainWindow()
{
InitializeComponent();
_timerThread = new System.Threading.Timer((o) =>
{
// Stop the timer;
_timerThread.Change(-1, -1);
// Process your data
ProcessData();
// start timer again (BeginTime, Interval)
_timerThread.Change(_period, _period);
}, null, 0, _period);
}
private void ProcessData()
{
// do stuff;
}
私の投稿からPeriodicTaskFactoryを使用する here
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
Task task = PeriodicTaskFactory.Start(() =>
{
Console.WriteLine(DateTime.Now);
Thread.Sleep(5000);
}, intervalInMilliseconds: 1000, synchronous: true, cancelToken: cancellationTokenSource.Token);
Console.WriteLine("Press any key to stop iterations...");
Console.ReadKey(true);
cancellationTokenSource.Cancel();
Console.WriteLine("Waiting for the task to complete...");
Task.WaitAny(task);
以下の出力は、間隔が1000ミリ秒に設定されていても、タスクアクションの作業が完了するまで各反復が開始されないことを示しています。これは、synchronous: true
オプションのパラメーター。
Press any key to stop iterations...
9/6/2013 1:01:52 PM
9/6/2013 1:01:58 PM
9/6/2013 1:02:04 PM
9/6/2013 1:02:10 PM
9/6/2013 1:02:16 PM
Waiting for the task to complete...
Press any key to continue . . .
[〜#〜] update [〜#〜]
PeriodicTaskFactoryで「スキップされたイベント」動作が必要な場合は、単に同期オプションを使用せず、Bobがここで行ったようにMonitor.TryEnterを実装しないでください https://stackoverflow.com/a/18665948/222434 =
Task task = PeriodicTaskFactory.Start(() =>
{
if (!Monitor.TryEnter(_locker)) { return; } // Don't let multiple threads in here at the same time.
try
{
Console.WriteLine(DateTime.Now);
Thread.Sleep(5000);
}
finally
{
Monitor.Exit(_locker);
}
}, intervalInMilliseconds: 1000, synchronous: false, cancelToken: cancellationTokenSource.Token);
PeriodicTaskFactory
の良いところは、すべてのTPL APIで使用できるタスクが返されることです。 Task.Wait
、継続など。
タイマーのコールバックをバックグラウンドスレッドで起動する場合は、 System.Threading.Timer を使用できます。このTimerクラスを使用すると、「Timeout.Infinite
は定期的なシグナリングを無効にします。」 constructor の一部として、タイマーを1回だけ起動します。
その後、最初のタイマーのコールバックが発生して完了したときに新しいタイマーを作成し、発生する準備が整うまで複数のタイマーがスケジュールされないようにすることができます。
ここでの利点は、一度に「次のイベント」以上のスケジュールを設定することはないため、タイマーを作成せずに繰り返しキャンセルすることです。
これを実現するには、タイマーとセマフォの使用、揮発性変数、TPLの使用、Quartzなどのオープンソーススケジューリングツールの使用など、少なくとも20の異なる方法があります。
スレッドを作成するのは費用がかかるため、1つだけを作成してバックグラウンドで実行したままにしないでください。IDLEの時間の大半を費やすため、システムに実際の流出は発生しません。定期的に目を覚まし、仕事をしてから、一定時間スリープ状態に戻ります。タスクにどれだけ時間がかかっても、完了してから新しいものを開始するまでに、少なくとも「waitForWork」のタイムスパンを常に待機します。
//wait 5 seconds for testing purposes
static TimeSpan waitForWork = new TimeSpan(0, 0, 0, 5, 0);
static ManualResetEventSlim shutdownEvent = new ManualResetEventSlim(false);
static void Main(string[] args)
{
System.Threading.Thread thread = new Thread(DoWork);
thread.Name = "My Worker Thread, Dude";
thread.Start();
Console.ReadLine();
shutdownEvent.Set();
thread.Join();
}
public static void DoWork()
{
do
{
//wait for work timeout or shudown event notification
shutdownEvent.Wait(waitForWork);
//if shutting down, exit the thread
if(shutdownEvent.IsSet)
return;
//TODO: Do Work here
} while (true);
}
System.Threading.Timerを使用できます。トリックは、初期時間のみを設定することです。前の間隔が終了するか、ジョブが終了すると、初期時間が再び設定されます(これは、ジョブが間隔より長くかかっている場合に発生します)。サンプルコードを次に示します。
class Program
{
static System.Threading.Timer timer;
static bool workAvailable = false;
static int timeInMs = 5000;
static object o = new object();
static void Main(string[] args)
{
timer = new Timer((o) =>
{
try
{
if (workAvailable)
{
// do the work, whatever is required.
// if another thread is started use Thread.Join to wait for the thread to finish
}
}
catch (Exception)
{
// handle
}
finally
{
// only set the initial time, do not set the recurring time
timer.Change(timeInMs, Timeout.Infinite);
}
});
// only set the initial time, do not set the recurring time
timer.Change(timeInMs, Timeout.Infinite);
}
タイマーをMonitor.TryEnter()
で使用しないのはなぜですか?前のスレッドが終了する前にOnTimerElapsed()
が再び呼び出された場合、それは単に破棄され、タイマーが再び作動するまで別の試行は再び行われません。
private static readonly object _locker = new object();
private void OnTimerElapsed(object sender, ElapsedEventArgs e)
{
if (!Monitor.TryEnter(_locker)) { return; } // Don't let multiple threads in here at the same time.
try
{
// do stuff
}
finally
{
Monitor.Exit(_locker);
}
}
タスクの前にタイマーを停止し、タスクの完了後に再び開始することができます。これにより、一定の間隔で定期的にテイクを実行できます。
public void myTimer_Elapsed(object sender, EventArgs e)
{
myTimer.Stop();
// Do something you want here.
myTimer.Start();
}
この質問には、 やや新しい TPLの機能のいくつかに基づいたものを含む、いくつかの良い答えが既にあります。しかし、私はここに欠けていると感じています:
async
/await
を使用する方法を示していません単一の方法で、c)参照される実装はかなり複雑であり、this特定の質問への基礎となる関連ポイントを多少難読化します。Task
とasync
/await
を使用して周期的な動作を実装することが特に好きです。特にasync
/await
機能はsoであり、そうでなければ継続によって破壊されるコードを取得するのに役立ちますコールバックの実装の詳細、およびその自然な線形ロジックを単一のメソッドで保持します。しかし、ここで答えはその単純さを実証しません。この理由から、この質問にさらに別の答えを追加するよう動機付けられました…
最初に考慮すべきことは、「(exact動作がここで望まれているのは何ですか?」ここでの質問は、基本的な前提から始まります。タイマーによって開始される期間タスクは、タスクがタイマー期間より長くかかっても、同時に実行されるべきではないということです。ただし、次のような前提を満たせる方法は複数あります。
コメントに基づいて、私は#3オプションがOPの元の要求に最もよく一致するという印象を持っていますが、#1オプションもおそらく機能すると思われます。しかし、オプション#2と#4は他の誰かよりも望ましいかもしれません。
次のコード例では、これらのオプションを5つの異なる方法で実装しました(そのうちの2つはオプション#3を実装しますが、方法が少し異なります)。もちろん、必要に応じて適切な実装を選択します。 1つのプログラムで5つすべてを使用する必要はないでしょう! :)
重要な点は、これらのすべての実装で、当然ながら非常に簡単な方法で、周期的だが非並行的な方法でタスクを実行することです。つまり、タイマーの実行モデルを効果的に実装する一方で、質問のプライマリリクエストごとにタスクが一度に1つのスレッドだけで実行されるようにします。
この例は、CancellationTokenSource
を使用して期間タスクを中断し、await
を利用して例外ベースのモデルをクリーンでシンプルな方法で処理する方法も示しています。
class Program
{
const int timerSeconds = 5, actionMinSeconds = 1, actionMaxSeconds = 7;
static Random _rnd = new Random();
static void Main(string[] args)
{
Console.WriteLine("Press any key to interrupt timer and exit...");
Console.WriteLine();
CancellationTokenSource cancelSource = new CancellationTokenSource();
new Thread(() => CancelOnInput(cancelSource)).Start();
Console.WriteLine(
"Starting at {0:HH:mm:ss.f}, timer interval is {1} seconds",
DateTime.Now, timerSeconds);
Console.WriteLine();
Console.WriteLine();
// NOTE: the call to Wait() is for the purpose of this
// specific demonstration in a console program. One does
// not normally use a blocking wait like this for asynchronous
// operations.
// Specify the specific implementation to test by providing the method
// name as the second argument.
RunTimer(cancelSource.Token, M1).Wait();
}
static async Task RunTimer(
CancellationToken cancelToken, Func<Action, TimeSpan, Task> timerMethod)
{
Console.WriteLine("Testing method {0}()", timerMethod.Method.Name);
Console.WriteLine();
try
{
await timerMethod(() =>
{
cancelToken.ThrowIfCancellationRequested();
DummyAction();
}, TimeSpan.FromSeconds(timerSeconds));
}
catch (OperationCanceledException)
{
Console.WriteLine();
Console.WriteLine("Operation cancelled");
}
}
static void CancelOnInput(CancellationTokenSource cancelSource)
{
Console.ReadKey();
cancelSource.Cancel();
}
static void DummyAction()
{
int duration = _rnd.Next(actionMinSeconds, actionMaxSeconds + 1);
Console.WriteLine("dummy action: {0} seconds", duration);
Console.Write(" start: {0:HH:mm:ss.f}", DateTime.Now);
Thread.Sleep(TimeSpan.FromSeconds(duration));
Console.WriteLine(" - end: {0:HH:mm:ss.f}", DateTime.Now);
}
static async Task M1(Action taskAction, TimeSpan timer)
{
// Most basic: always wait specified duration between
// each execution of taskAction
while (true)
{
await Task.Delay(timer);
await Task.Run(() => taskAction());
}
}
static async Task M2(Action taskAction, TimeSpan timer)
{
// Simple: wait for specified interval, minus the duration of
// the execution of taskAction. Run taskAction immediately if
// the previous execution too longer than timer.
TimeSpan remainingDelay = timer;
while (true)
{
if (remainingDelay > TimeSpan.Zero)
{
await Task.Delay(remainingDelay);
}
Stopwatch sw = Stopwatch.StartNew();
await Task.Run(() => taskAction());
remainingDelay = timer - sw.Elapsed;
}
}
static async Task M3a(Action taskAction, TimeSpan timer)
{
// More complicated: only start action on time intervals that
// are multiples of the specified timer interval. If execution
// of taskAction takes longer than the specified timer interval,
// wait until next multiple.
// NOTE: this implementation may drift over time relative to the
// initial start time, as it considers only the time for the executed
// action and there is a small amount of overhead in the loop. See
// M3b() for an implementation that always executes on multiples of
// the interval relative to the original start time.
TimeSpan remainingDelay = timer;
while (true)
{
await Task.Delay(remainingDelay);
Stopwatch sw = Stopwatch.StartNew();
await Task.Run(() => taskAction());
long remainder = sw.Elapsed.Ticks % timer.Ticks;
remainingDelay = TimeSpan.FromTicks(timer.Ticks - remainder);
}
}
static async Task M3b(Action taskAction, TimeSpan timer)
{
// More complicated: only start action on time intervals that
// are multiples of the specified timer interval. If execution
// of taskAction takes longer than the specified timer interval,
// wait until next multiple.
// NOTE: this implementation computes the intervals based on the
// original start time of the loop, and thus will not drift over
// time (not counting any drift that exists in the computer's clock
// itself).
TimeSpan remainingDelay = timer;
Stopwatch swTotal = Stopwatch.StartNew();
while (true)
{
await Task.Delay(remainingDelay);
await Task.Run(() => taskAction());
long remainder = swTotal.Elapsed.Ticks % timer.Ticks;
remainingDelay = TimeSpan.FromTicks(timer.Ticks - remainder);
}
}
static async Task M4(Action taskAction, TimeSpan timer)
{
// More complicated: this implementation is very different from
// the others, in that while each execution of the task action
// is serialized, they are effectively queued. In all of the others,
// if the task is executing when a timer tick would have happened,
// the execution for that tick is simply ignored. But here, each time
// the timer would have ticked, the task action will be executed.
//
// If the task action takes longer than the timer for an extended
// period of time, it will repeatedly execute. If and when it
// "catches up" (which it can do only if it then eventually
// executes more quickly than the timer period for some number
// of iterations), it reverts to the "execute on a fixed
// interval" behavior.
TimeSpan nextTick = timer;
Stopwatch swTotal = Stopwatch.StartNew();
while (true)
{
TimeSpan remainingDelay = nextTick - swTotal.Elapsed;
if (remainingDelay > TimeSpan.Zero)
{
await Task.Delay(remainingDelay);
}
await Task.Run(() => taskAction());
nextTick += timer;
}
}
}
最後の注意点:別の質問の複製としてこのQ&Aを見つけました。この他の質問では、こことは異なり、OPはSystem.Windows.Forms.Timer
クラスを使用していることに明確に言及していました。もちろん、このクラスは主にUIスレッドでTick
イベントが発生するという素晴らしい機能があるために使用されます。
現在、それとこの質問の両方には、バックグラウンドスレッドで実際に実行されるタスクが関係しているため、そのタイマークラスのUIスレッド関連の動作は、これらのシナリオでは実際には特に使用されません。ここのコードは、その「バックグラウンドタスクを開始する」パラダイムに一致するように実装されていますが、taskAction
デリゲートがTask
で実行されるのではなく、単に直接呼び出されるように簡単に変更できます待った。上記の構造的利点に加えて、async
/await
を使用することの良いところは、System.Windows.Forms.Timer
クラスに望ましいスレッド関連の動作が保持されることです。
私が正しく理解していれば、実際には、別のスレッドをディスパッチする前にスレッドが実行されていないことを確認したいだけです。クラスでスレッドが定義されているとしましょうlike so。
private System.Threading.Thread myThread;
あなたができる:
//inside some executed method
System.Threading.Timer t = new System.Threading.Timer(timerCallBackMethod, null, 0, 5000);
その後コールバックを追加します
private void timerCallBackMethod(object state)
{
if(myThread.ThreadState == System.Threading.ThreadState.Stopped || myThread.ThreadState == System.Threading.ThreadState.Unstarted)
{
//dispatch new thread
}
}
しばらく前に同じ問題が発生しましたが、 lock {} ステートメントを使用するだけでした。これにより、タイマーが何かを実行したい場合でも、ロックブロックが終了するまで待機する必要があります。
つまり.
lock
{
// this code will never be interrupted or started again until it has finished
}
これは、プロセスが中断することなく最後まで機能することを確実にする素晴らしい方法です。
これはあなたが望むことをするはずです。スレッドを実行し、終了するまでスレッドに参加します。タイマーループに入り、スレッドが時期尚早に実行されていないことを確認してから、再びオフになって実行されます。
using System.Threading;
public class MyThread
{
public void ThreadFunc()
{
// do nothing apart from sleep a bit
System.Console.WriteLine("In Timer Function!");
Thread.Sleep(new TimeSpan(0, 0, 5));
}
};
class Program
{
static void Main(string[] args)
{
bool bExit = false;
DateTime tmeLastExecuted;
// while we don't have a condition to exit the thread loop
while (!bExit)
{
// create a new instance of our thread class and ThreadStart paramter
MyThread myThreadClass = new MyThread();
Thread newThread = new Thread(new ThreadStart(myThreadClass.ThreadFunc));
// just as well join the thread until it exits
tmeLastExecuted = DateTime.Now; // update timing flag
newThread.Start();
newThread.Join();
// when we are in the timing threshold to execute a new thread, we can exit
// this loop
System.Console.WriteLine("Sleeping for a bit!");
// only allowed to execute a thread every 10 seconds minimum
while (DateTime.Now - tmeLastExecuted < new TimeSpan(0, 0, 10));
{
Thread.Sleep(100); // sleep to make sure program has no tight loops
}
System.Console.WriteLine("Ok, going in for another thread creation!");
}
}
}
次のようなものを生成する必要があります。
タイマー機能で!少し寝ています! OK、別のスレッドを作成します!タイマー機能で!少し寝ています! OK、別のスレッドを作成します!タイマー機能で! ... ...
お役に立てれば! SR
この基本はExecuteTaskCallback
メソッドです。このビットは、まだ実行していない場合にのみ、いくつかの作業を行うことを担当します。このために、最初にManualResetEvent
メソッドで通知されるように設定されているcanExecute
(StartTaskCallbacks
)を使用しました。
canExecute.WaitOne(0)
の使用方法に注意してください。ゼロは、WaitOne
がWaitHandle
の状態ですぐに戻ることを意味します( (〜#〜] msdn [〜#〜] )。ゼロを省略すると、ExecuteTaskCallback
を呼び出すたびに最終的にタスクを実行することになり、かなり悲惨な結果になる可能性があります。
他の重要なことは、処理をきれいに終了できることです。 Timer
がStopTaskCallbacks
にある他のメソッドを実行しないようにすることを選択しました。他の作業が進行中の場合は、そうすることが望ましいと思われるためです。これにより、新しい作業が行われず、canExecute.WaitOne();
への後続の呼び出しが実際に最後のタスクをカバーします(ある場合)。
private static void ExecuteTaskCallback(object state)
{
ManualResetEvent canExecute = (ManualResetEvent)state;
if (canExecute.WaitOne(0))
{
canExecute.Reset();
Console.WriteLine("Doing some work...");
//Simulate doing work.
Thread.Sleep(3000);
Console.WriteLine("...work completed");
canExecute.Set();
}
else
{
Console.WriteLine("Returning as method is already running");
}
}
private static void StartTaskCallbacks()
{
ManualResetEvent canExecute = new ManualResetEvent(true),
stopRunning = new ManualResetEvent(false);
int interval = 1000;
//Periodic invocations. Begins immediately.
Timer timer = new Timer(ExecuteTaskCallback, canExecute, 0, interval);
//Simulate being stopped.
Timer stopTimer = new Timer(StopTaskCallbacks, new object[]
{
canExecute, stopRunning, timer
}, 10000, Timeout.Infinite);
stopRunning.WaitOne();
//Clean up.
timer.Dispose();
stopTimer.Dispose();
}
private static void StopTaskCallbacks(object state)
{
object[] stateArray = (object[])state;
ManualResetEvent canExecute = (ManualResetEvent)stateArray[0];
ManualResetEvent stopRunning = (ManualResetEvent)stateArray[1];
Timer timer = (Timer)stateArray[2];
//Stop the periodic invocations.
timer.Change(Timeout.Infinite, Timeout.Infinite);
Console.WriteLine("Waiting for existing work to complete");
canExecute.WaitOne();
stopRunning.Set();
}
オブジェクトが軽いため、スレッドではなくTimerを使用することをお勧めします。目標を達成するために、次のことができます。
using System.Timers;
namespace sample_code_1
{
public class ClassName
{
Timer myTimer;
static volatile bool isRunning;
public OnboardingTaskService()
{
myTimer= new Timer();
myTimer.Interval = 60000;
myTimer.Elapsed += myTimer_Elapsed;
myTimer.Start();
}
private void myTimer_Elapsed(object sender, ElapsedEventArgs e)
{
if (isRunning) return;
isRunning = true;
try
{
//Your Code....
}
catch (Exception ex)
{
//Handle Exception
}
finally { isRunning = false; }
}
}
}
役立つかどうか教えてください。