2009-09-04 39 views
6

私は、別のスレッド(threadJobの子)上でジョブを(一度に1つずつ)起動することになるサービスを持っています。これらのジョブはかなりの時間がかかります。子スレッドはどのように親スレッドに状態/進捗状況を通知できますか?

は、私は彼らに報告する必要があります。

これまでそれほど頻繁に呼び出し側のアプリケーションがサービス(のGetStatus)からステータスを要求、これは何とかサービスは私の希望がでていることだった、仕事(子スレッド)が

であるかの時点で知っておく必要があることを意味しいくつかのマイルストーンは、子スレッドが状態の親スレッド(サービス)に何とか(SetStatus)通知することができ、サービスはその情報を呼び出し側アプリケーションに返すことができる。

ジョブが呼び出された()RunTaskを実行する必要があるので、

class Service 
{ 
private Thread threadJob; 
private int JOB_STATUS; 

public Service() 
    { 
    JOB_STATUS = "IDLE"; 
    } 

public void RunTask() 
    { 
    threadJob = new Thread(new ThreadStart(PerformWork)); 
    threadJob.IsBackground = true; 
    threadJob.Start(); 
    } 

public void PerformWork() 
    { 
    SetStatus("STARTING"); 
    // do some work // 
    SetStatus("PHASE I"); 
    // do some work // 
    SetStatus("PHASE II"); 
    // do some work // 
    SetStatus("PHASE III"); 
    // do some work // 
    SetStatus("FINISHED"); 
    } 

private void SetStatus(int status) 
    { 
    JOB_STATUS = status; 
    } 

public string GetStatus() 
    { 
    return JOB_STATUS; 
    } 

}; 

を、このスレッド(threadJob)を起動します: - たとえば

私はこのような何かを探していました。これはいくつかのステップを実行して実行します(SetStatusを使用して新しいステータスを

さまざまなポイントに設定します)。現在、(IPCを使用している呼び出し側アプリケーションから)要求されるたびにSTATUSを返す関数GetStatus()もあります。このステータスは、threadJobによって実行されているジョブの現在のステータスを反映する必要があります。

ThreadJob(または具体的にはPerformWork())がスレッドセーフな方法でステータスの変更をサービスする方法(SetStatus/GetStatusの上記の例は

unsafe)?イベントを使用する必要がありますか?私は単純にJOB_STATUSを直接変更することはできないと思う... ...もしもロックを使うべきであれば...?

答えて

2

イベントをServiceクラスで作成し、それをスレッドセーフな方法で呼び出すことができます。私がSetStatusメソッドをどのように実装したかには細心の注意を払ってください。

class Service 
{ 
    public delegate void JobStatusChangeHandler(string status); 

    // Event add/remove auto implemented code is already thread-safe. 
    public event JobStatusHandler JobStatusChange; 

    public void PerformWork() 
    { 
    SetStatus("STARTING"); 
    // stuff 
    SetStatus("FINISHED"); 
    } 

    private void SetStatus(string status) 
    { 
    JobStatusChangeHandler snapshot; 
    lock (this) 
    { 
     // Get a snapshot of the invocation list for the event handler. 
     snapshot = JobStatusChange; 
    } 

    // This is threadsafe because multicast delegates are immutable. 
    // If you did not extract the invocation list into a local variable then 
    // the event may have all delegates removed after the check for null which 
    // which would result in a NullReferenceException when you attempt to invoke 
    // it. 
    if (snapshot != null) 
    { 
     snapshot(status); 
    } 
    } 
} 
+0

サンプルがどのように動作するかは100%明確ではありませんSetStatusの呼び出し時にステータスが実際にどのように設定されているかは、スナップショット(ステータス)でのみ使用されますが、デリゲートJobStatusChangHandler また、どのようにのGetStatusは、この場合には動作しますか? – Shaitan00

+0

呼び出し元は、まずJobStatusChangeイベントをサブスクライブする必要があります。スナップショット(ステータス)が呼び出されると、すべてのイベントハンドラが呼び出されます。 GetStatusを介してステータスをポーリングする代わりに、イベント駆動型メソッドを使用してステータスが変化するたびに、Serviceクラスが呼び出し元に通知します。 –

+0

あなたのクラスをロックしようとする人が他にいないかどうかわからないので、lock(これ)を使用することに注意してください。これは完全に安全ではありませんが、それはデッドロックを招きます。 –

8

あなたは既にこれを調べているかもしれないが、BackgroundWorkerクラスは、進行状況が変更されたという通知を受け取るためのイベントを提供します。

+0

イベントは確かに最良の方法です。 – Juri

2

私は子スレッドに 'statusupdate'イベントを発生させ、親に必要な情報を含む構造体を渡して、それを起動するときに親にサブスクライブするようにします。

0

スレッドから代理人/イベントを呼び出し元に送信します。呼び出し元がUIまたはその行のどこかにあった場合は、メッセージポンプにうってつけで、適切なInvoke()を使用して、必要に応じてUIスレッドで通知をシリアル化します。

+0

発信者がUIではない - 現実にはまったくこのアプリケーションにはUIはありません... – Shaitan00

+0

それから私は、イベントを使用して、問題が表示されません。 –

0

私はかつてスレッドが作成していた進行状況を示すマーカーを必要とするアプリケーションを書いた。私はちょうどそれらの間で共有グローバル変数を使用しました。親は値を読み取るだけで、スレッドはそれを更新します。親だけがそれを読むように同期する必要はなく、子供だけがそれを原子的に書きました。それが起こったときに、親が何かを頻繁に再描画していたので、子供が変数を更新したときに子が突き刺す必要さえもしなかった。場合によっては、最も単純な方法でうまくいくこともあります。

0

現在のコードでは、文字列とintを組み合わせて使用​​することはできませんJOB_STATUSです。私はここで文字列を仮定していますが、実際には問題ではありません。参照型のフィールドへのすべての割り当てがアトミックであることが保証されているので


あなたは現在の実装では、何のメモリの破損が発生しないという意味では、スレッドセーフです。 CLRはこれを要求します。そうしないと、部分的に更新された参照に何らかの形でアクセスできる場合、アンマネージメモリにアクセスする可能性があります。しかし、あなたのプロセッサーはそのアトミック性を無料で提供します。

文字列などの参照型を使用している限り、メモリの破損は発生しません。同じことは、int(それより小さい)やenumなどのプリミティブにも当てはまります。 (ちょうどそのようなNULL可能整数としてlongと大きな、および非プリミティブな値のタイプを避ける。)

しかしが、それは物語の終わりではありません。この実装は常に現在の状態を表現することは保証されません。その理由は、を呼び出すスレッドは、フィールドの無効なコピーを見ている可能性があります。SetStateの割り当てには、いわゆるメモリバリアが含まれていないためです。つまり、JOB_STATUSの新しい値をメインRAMにすぐに送信する必要はありません。これが遅延することができますいくつかの理由があります。

  1. メインRAMへの書き込みはそう、あなたのプロセッサが最初の場所ですべてのバッファの種類とL-何かキャッシュを持っている理由である、(相対的に言って)本質的に遅いですプロセッサは通常、メモリ同期を遅延させる。非常に長くはないが、おそらく遅れるだろう。これは通常、コアごとに別々のキャッシュを持つため、マルチコアプロセッサではかなり目立つことがあります。
  2. JITは、いくつかの最適化戦略の一部として、先にレジスタJOB_STATUSをレジスタに格納している可能性があります。繰り返しになりますが、レジスタは主RAMよりもはるかに効率的です。しかし、これは、登録簿の古いコピーをまだ見ているので、十分早期に変更が見られないことを意味します。 (ここでは分を話して、まだしていない。)

だから、あなたはそれぞれのスレッド&プロセッサ・コアは、volatileとして、あなたのフィールドを宣言し、すぐに変更された状態を認識していることを100%と一定にしたい場合:

プロセッサはそれを行うことができれば今、 GetStatus/SetStatusは、任意のロック構造なしで、本当に、値が すぐ(または何か100%相当をから読み取られ、メインRAMに書き込まれ volatile要望として、スレッドセーフされ
private volatile int JOB_STATUS; 

より効率的に)。あなたがvolatileとして、あなたのフィールドを宣言しない場合は、そうでなければ、解決することはできません、などlockとして、同期プリミティブを使用しますが、一般的には、同期プリミティブを使用して、両方のセットを取得する必要が話す必要があることを

注意volatileの問題を修正しました。

ステータスを取得するためにIPCコールを行っているときに、IPCコールのオーバーヘッドを考慮すると、実際には不揮発性と揮発性の違いを観察することはできません間違いなくシーンの裏で実行されるスレッド同期が含まれます。 volatileの詳細については

、MSDNのvolatile (C#)を参照してください。

+0

私の悪い....JOB_STATUSはSTRING(intではない)でなければなりません...私はまだあなたが言及した方法でvolatileを使用できますか? yop; – Shaitan00

+0

文字列フィールドは、揮発性 – Ruben

+0

宣言することができますしかし、現在の実装では、それなしでは技術的にスレッドセーフではありません、あなたはvolatileキーワードを言及したことは良いです。確かに、読み書き操作はアトミックですが、コードにはまだメモリバリアの問題があります。あなたはその点を確認しましたが、それは私にはあまりあいまいでした。 –

関連する問題