2011-01-21 7 views
2

私はbackgroundworkerでuserstateという概念を理解できないようです。 私が取り組んでいるアプリケーションで問題が発生しました。私が期待していなかったことが何か起こっている理由について説明が必要です。ProgressChangedコールバックでのBackgroundWorkerとUserStateの問題

私はもっと単純に問題を再現するデモアプリケーションを構築しています:

public class Tester 
{ 
    private BackgroundWorker _worker = new BackgroundWorker(); 

    public void performTest() 
    { 
     Tester tester = new Tester(); 
     tester.crunchSomeNumbers((obj, arg) => 
     { 
      WorkerArgument userState = arg.UserState as WorkerArgument; 
      Console.WriteLine(string.Format("Progress: {0}; Calculation result: {1}", arg.ProgressPercentage, userState.CalculationResult)); 
     }); 
    } 

    public void crunchSomeNumbers(Action<object,ProgressChangedEventArgs> onProgressChanged) 
    { 
     _worker.DoWork += new DoWorkEventHandler(worker_DoWork); 
     _worker.ProgressChanged += new ProgressChangedEventHandler(onProgressChanged); 
     _worker.WorkerReportsProgress = true; 
     _worker.RunWorkerAsync(new WorkerArgument { CalculationResult=-1, BaseNumber = 10 }); 
    } 

    void worker_DoWork(object sender, DoWorkEventArgs e) 
    { 
     BackgroundWorker worker = sender as BackgroundWorker; 
     WorkerArgument arg = e.Argument as WorkerArgument; 

     for (int i = 0; i < 10; i++) 
     { 
      // calculate total with basenumber 
      double result = arg.BaseNumber * (i * 10); 
      arg.CalculationResult = result; 
      worker.ReportProgress(i * 10, arg); 
     } 
    } 

    public class WorkerArgument 
    { 
     public int BaseNumber { get; set; } 
     public double CalculationResult { get; set; } 
    } 
} 

コンソールでこのコードを実行した場合のApp:

class Program 
{ 
    static void Main(string[] args) 
    { 
     Tester tester = new Tester(); 
     tester.performTest(); 

     Console.ReadLine(); 
    } 
} 

これは結果である:

http://img684.imageshack.us/img684/1509/bgwproblem.png

私が理解できないのは、計算結果がDoWorkメソッドのforloopで実行される各計算で異なる必要があることがはっきり分かりますが、同じことを繰り返す必要があります。

+0

これはテストケースのエラーのように見えますが。コンソールアプリケーションでバックグラウンドワーカーを使用/テストしないでください。これはシングルスレッドで実行されます。 –

+1

コンソールアプリケーションでバックグラウンドスレッドを実行すると何が問題になりますか。バックグラウンドスレッドを持つ他のUIと同じくらい便利です。 – btlog

+0

@btlog:お読みください。スレッドには何も問題はありませんが、Bgwにはメッセージポンプが必要です。 –

答えて

1

は二つの問題があります。イベントハンドラは、それが現在のWorkerArgument値、イベントが発生した時にはなかったというものを持って実行された場合、その -

arg.CalculationResult = result; 
worker.ReportProgress(i * 10, arg); 
Thread.Sleep(500); 

2)あなたはWorkerArgumentの1つのインスタンスを使用しています。これを見ても、イベントを起こすときに引数の新しいインスタンスを渡すだけです。

worker.ReportProgress(i * 10, new WorkerArgument(){ CalculationResult = result }); 
+2

同じ変数が変更されることが予想されるので、これはクロージャの問題ではないと思います。クロージャの問題は、同じ変数の意図しない使用を示す傾向があります。 – btlog

+0

+1私は毎回WorkerArgumentの新しいインスタンスを返すことによって特定のルールを破ることはないと思うので、私はあなたのソリューションを使用し、それは動作します。あなたの助けと解決に感謝します!私は今それを理解しています:-) – Peter

+0

BTW単純な基底番号をRunWorkerAsyncに渡すだけで、WorkerArgumentを使わずに簡単な結果を返すことができます:_worker.RunWorkerAsync(baseNumber); worker.ReportProgress(i * 10、result); –

0

私は問題を完全に見つけることはできませんが、おそらく匿名の方法と変数の固定についてです。それぞれの匿名メソッドまたはラムダを適切なメソッドとして書き直し、問題が解決されないかどうかを確認して絞り込んでください。

+0

これは匿名メソッドの使用とは関係ありません。 – Peter

2

ループの次の反復の前にイベントが発生して実行されることが前提です。残念ながら、これは正しくありません。

あなたのforループが最初のイベントが実行される前に完了していることが起こっています。したがって、Console.WriteLineを呼び出す前に、userState.CalculationResultは900になります。あなたは

for (int i = 0; < 1000000; i++) 

あなたは数の増加があることを確認する必要がありにトップのためのために変更した場合、しかし、すべてのイベントが実行される前に、それが最大数になります。

もう一つの方法は、worker.ReportProgressの呼び出しの前にConsole.WriteLineを置くことです。 forループの完了の順序がイベントレポートと異なることがわかります。 Console.WriteLineは実際には遅い呼び出しであり、forループの実行が大幅に遅くなるため、最初のイベントコード出力では完全ではありません。

マルチスレッドで覚えておくべきエキサイティングな問題の1つは、呼び出しイベントが非ブロックであることです。最初のイベントハンドラを使用すると、ReportProgressを呼び出した後、バックグラウンドワーカースレッドの実行を凍結している場合は、それを見ることができ 実行する前にループが実行される

1):

+0

writelineはいい考えです。また、ThreadIdを報告してください、私の推測では、このすべてがシングルスレッドで実行されています。 –

+0

+1これは問題を非常に正確に説明しています。情報ありがとうございました! – Peter

0

あなたが渡された労働者を呼び出すことになるでしょう:

void worker_DoWork(object sender, DoWorkEventArgs e) 
{ 
    BackgroundWorker this_worker = sender as BackgroundWorker; 
    WorkerArgument arg = e.Argument as WorkerArgument; 

    for (int i = 0; i < 10; i++) 
    { 
     // calculate total with basenumber 
     double result = arg.BaseNumber * (i * 10); 
     arg.CalculationResult = result; 
     this_worker.ReportProgress(i * 10, arg); 
    } 
} 
+0

これと作業者を定義するクラス変数を使用する違いは何ですか?コードに作成されたバックグラウンドワーカーは1人だけです。あなたの例は、this_workerがworkerと同じオブジェクトであるのと本質的に同じです。それとも、私は落ち着きを欠いていますか? – btlog

+0

@btlog - 違いは、OPはメインスレッドのBackgroundWorkerを使用していることです。 DoWorkは別のスレッドで実行されるため、呼び出すべきではありません。私はそれが問題をここで解決するとは思っていませんが、間違いなく問題です。 – SwDevMan81

+0

私はそれが問題であるかどうかわかりません共有メモリに存在するインスタンス化されたオブジェクトです。どのスレッドがそれを使用しているかは関係ありません。 – btlog

関連する問題