web-dev-qa-db-ja.com

C#でコードを無限に/定期的に実行するためのベストプラクティス

多くの場合、私のコードでは、基本的に次のような脅威を開始します。

void WatchForSomething()
{
    while(true)
    {
        if(SomeCondition)
        {
             //Raise Event to handle Condition
             OnSomeCondition();
        }
        Sleep(100);
    }
}

ある条件が真であるかどうかを知るためだけに(たとえば、イベントのない悪いコード化ライブラリがある場合、ブール変数だけで、それらの「ライブビュー」が必要です)。

今、私は、ウィンドウ関数が私のメソッドをすべてx秒で実行できるようにフックするようなこの種の作業を達成するためのより良い方法があるかどうか疑問に思います。または、アプリのグローバルイベントをコーディングして、すべてのx秒を上げ、次のようにメソッドを呼び出させる必要があります。

//Event from Windows or selfmade
TicEvent += new TicEventHandler(WatchForSomething));

そしてこの方法:

    void WatchForSomething()
    {
        if(SomeCondition)
        {
             //Raise Event to handle Condition
             OnSomeCondition();
        }
    }

ですから、「主観的な質問」などでこれが閉じられないことを願っています。この種の作業のベストプラクティスが何であるかを知りたいだけです。

23
Tokk

長時間実行されるイベント処理コードを書くための「最良の方法」は必ずしもありません。開発しているアプリケーションの種類によって異なります。

最初に示す例は、長時間実行されるスレッドのメインメソッドが記述されているのをよく見かける慣用的な方法です。一般的には使用することが望ましいですがSleep()の呼び出しではなく、ミューテックスまたは待機可能なイベント同期プリミティブ-それ以外の場合は、イベント処理ループを実装するために使用される一般的なパターンです。このアプローチの利点は、特殊な処理を別のスレッドで実行できることです。つまり、アプリケーションのメインスレッドが他のタスクを実行したり、ユーザー入力に応答したりできるようになります。このアプローチの欠点は、共有リソースが破損しないようにするために、メモリバリア(ロックなど)の使用が必要になる場合があることです。また、通常、そのような呼び出しをUIスレッドにマーシャリングする必要があるため、UIの更新がより困難になります。

2番目のアプローチもよく使用されます-特にWinForms、WPF、SilverlightなどのイベントドライブAPIがすでにあるシステムで使用されます。タイマーの使用オブジェクトまたはアイドルイベントは、処理をトリガーするユーザーが開始したイベントがない場合に定期的なバックグラウンドチェックを実行できる一般的な方法です。ここでの利点は、ユーザーインターフェイスオブジェクトの操作と更新が簡単で(同じスレッドから直接アクセスできるため)、保護されたデータへのロックとミューテックスの必要性が軽減されることです。このアプローチの潜在的な欠点の1つは、実行する必要のある処理に時間がかかる場合、アプリケーションがユーザー入力に応答しなくなる可能性があることです。

ユーザーインターフェイス(サービスなど)を備えたアプリケーションを作成していない場合は、最初の形式がはるかに頻繁に使用されます。

余談ですが...可能であれば、 EventWaitHandleSemaphore などの同期オブジェクトを使用して作業が可能になったときに通知することをお勧めします。処理されます。これにより、Thread.SleepオブジェクトやTimerオブジェクトの使用を回避できます。作業を実行できるようになってからイベント処理コードがトリガーされるまでの平均レイテンシーが短縮され、ランタイム環境によってより効率的にスケジュールでき、CPUサイクルを消費しないため、バックグラウンドスレッドを使用するオーバーヘッドが最小限に抑えられます。やるべき仕事があるまで。

また、実行する処理が外部ソース(MessageQueues、HTTP、TCPなど)との通信に応答する場合は、 [〜#〜] wcf [〜#〜] のようなテクノロジを使用できることにも言及する価値があります。イベント処理コードのスケルトンを提供します。 WCFは、通信イベントアクティビティに非同期的に応答するクライアントシステムとサーバーシステムの両方の実装を大幅に容易にする基本クラスを提供します。

18
LBushkin

Reactive Extensions を見ると、 observable pattern を使用してこれを行うエレガントな方法が提供されます。

var timer = Observable.Interval(Timespan.FromMilliseconds(100));
timer.Subscribe(tick => OnSomeCondition());

オブザーバブルの優れた点は、既存のオブザーバブルからさらにオブザーバブルを作成して結合し、LINQ式を使用して新しいオブザーバブルを作成できることです。たとえば、最初のタイマーと同期しているが、1秒ごとにのみトリガーされる2番目のタイマーが必要な場合は、次のように言うことができます。

var seconds = from tick in timer where tick % 10 == 0 select tick;
seconds.Subscribe(tick => OnSomeOtherCondition());
12
Mark H

ちなみに、Thread.Sleepはおそらく決して良い考えではありません。

人々が通常気づいていないThread.Sleepの基本的な問題は、Thread.SleepSTAメッセージをポンプしないの内部実装です。一定の時間を待つ必要があり、カーネル同期オブジェクトを使用できない場合の最善かつ最も簡単な代替方法は、現在のスレッドでThread.SleepThread.Joinに置き換え、必要なタイムアウトを設定することです。 Thread.Joinは同じように動作します。つまり、スレッドは必要な時間待機しますが、その間にSTAオブジェクトがポンプされます。

なぜこれが重要なのですか(詳細な説明が続きます)。

場合によっては、知らないうちに、スレッドの1つがSTACOMオブジェクトを作成した可能性があります。 (たとえば、これはシェルAPIを使用するときに舞台裏で発生することがあります)。ここで、自分のスレッドがSTA COMオブジェクトを作成し、Thread.Sleepを呼び出しているとします。いつかCOMオブジェクトを削除する必要がある場合(GCによって予期しないときに発生する可能性があります)、ファイナライザスレッドはオブジェクトのディストラクタを呼び出そうとします。この呼び出しは、オブジェクトのSTAスレッドにマーシャリングされ、ブロックされます。

これで、実際には、ファイナライザースレッドがブロックされます。この状況では、オブジェクトをメモリから解放することはできず、悪いことが続きます。

つまり、結論はThread.Sleep = badです。 Thread.Join =合理的な代替案。

10
Ran

最初に示す例は、定期的なタイマーを実装するためのかなりエレガントでない方法です。 .NETには、この種のことをほとんど簡単にする多くのタイマーオブジェクトがあります。 System.Windows.Forms.TimerSystem.Timers.Timer、およびSystem.Threading.Timerを調べます。

たとえば、最初の例を置き換えるためにSystem.Threading.Timerを使用する方法は次のとおりです。

System.Threading.Timer MyTimer = new System.Threading.Timer(CheckCondition, null, 100, 100);

void CheckCondition(object state)
{
    if (SomeCondition())
    {
        OnSomeCondition();
    }
}

そのコードは、100ミリ秒(またはその前後)ごとにCheckConditionを呼び出します。

6
Jim Mischel

なぜこれを行っているのか、または何を達成しようとしているのかについては、多くの背景情報を提供していませんが、可能であれば、Windowsサービスの作成を検討することをお勧めします。

0
chris

追加のスレッドセーフ対策には、BackgroundWokerを使用します。

BackgroundWorker bw = new BackgroundWorker();


bw.WorkerSupportsCancellation = true;


bw.WorkerReportsProgress = true;
.
.
.
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;

    for (;;)
    {
        if (worker.CancellationPending == true)
        {
           e.Cancel = true;

           break;
        }

        else
        {
            // Perform a time consuming operation and report progress.

            System.Threading.Thread.Sleep(100);
        }
    }
}

詳細については、次を参照してください: http://msdn.Microsoft.com/en-us/library/cc221403%28v=vs.95%29.aspx

0
LEMUEL ADANE

他のスレッド/タスクを非ブロッキング待機するための非常に簡単な方法は次のとおりです。

(new ManualResetEvent(false)).WaitOne(500); //Waits 500ms
0
WPFGermany