2016-06-28 14 views
3

私は、多くの行を持つファイルを処理するプログレスバーでwindowsフォームプログラムを作成しています。次のコードを実行すると、button2_ClickメソッドのprogressBar1.Maximum呼び出しはうまく実行されますが、PostIncidentメソッドの1つでは、次のようなSystem.InvalidOperationExceptionが発生します: "クロススレッド操作が有効でない:Control 'progressBar1'それが作成されたスレッド以外のスレッド "このコードをスレッドセーフにする方法はありますか?

これまで試したことがある道は です。1)PostIncidentは、progressbar1.Maximum呼び出しをbutton2_Clickメソッドにプルするように、ブール値またはマジック値を返します。 私の問題は、スレッディングに関するスキルが、スレッドから返された値をキャプチャする際の問題を回避するのに十分ではないことです。

2)プログレスバーの周りにロックまたはセマフォを入れようとしました。PostIncidentメソッドの最大呼び出しで、同じエラーが発生しました。

現時点では、私のソリューションは単にプロジェクトからのスレッドを完全に削除することですが、私は確かに経験のない優雅な解決策があると確信しています。

public partial class ExperianTriggerPoster : Form 
{ 
    private readonly OpenFileDialog _ofd = new OpenFileDialog(); 
    public delegate void BarDelegate(); 
    private string _path; 

    private void button1_Click(object sender, EventArgs e) 
    { 
     if (_ofd.ShowDialog() != DialogResult.OK) return; 
     textBox1.Text = _ofd.SafeFileName; 
     _path = _ofd.FileName; 
    } 

    private void button2_Click(object sender, EventArgs e) 
    { 
     string[] sAllLinesFromFile = File.ReadAllLines(_path); 

     foreach (string line in sAllLinesFromFile) 
     { 
      if (!line.StartsWith("N")) 
      { 
       progressBar1.Maximum -= 1; 
       continue; 
      } 

      //some logic here... 
      ThreadPool.QueueUserWorkItem(x => PostIncident(//some parameters here...)); 
     } 
    } 

    private void PostIncident(//some parameters here...) 
    { 
     //some logic here... 
     if (customerNo == "not found") // must find a way to make this call thread-safe 
     { 
      Log.Information("Could not find customer# for user#: " + userNo); 
      progressBar1.Maximum -= 1; 
     } 

     Invoke(new BarDelegate(UpdateBar)); 
    } 

    private void UpdateBar() 
    { 
     progressBar1.Value++; 

     if (progressBar1.Value != progressBar1.Maximum) return; 

     var postingComplete = MessageBox.Show("The posting is complete!", "Experian Trigger Poster", MessageBoxButtons.OK, MessageBoxIcon.Asterisk); 
     if (postingComplete == DialogResult.OK) Environment.Exit(0); 
    } 
} 

答えて

1

とても簡単です。

Invoke(new BarDelegate(() => progressBar1.Maximum -= 1)); 

あなたは

Invoke(new BarDelegate(UpdateBar)); 

を使用して知っているだけで、UIスレッドがUIコントロールへのアクセスを取得することを忘れないでください。UI以外のスレッドでUIコントロールのプロパティを割り当てまたは読み込もうとすると、例外が発生します。

ThreadPool.QueueUserWorkItemは、別のバックグラウンドスレッドでPostIncidentを実行します。 UIコントロールを操作するには、Invokeを使ってステートメントをラップしてください。

Invokeは、UIスレッドにステートメントを持ち込み、ステートメントを実行してから、非UIスレッドに戻ります。これは、UIスレッドと非UIスレッドの間のブリッジとして機能します。

また、UIスレッドがバックグラウンドスレッドを遅くしたくない場合は、BeginInvokeを使用できます。 BeginInvoke UI以外のスレッドを続行する前に、UIスレッドの実行を待機しません。レースの問題に注意してください。

+0

うわー私はこれをどうやって逃したのか分かりません。とても簡単。ありがとうございました! –

2

Safe, Simple Multithreading in Windows FormsHow to: Make Thread-Safe Calls to Windows Forms Controls上を見てみましょう。

UIスレッドでUI更新プログラムを呼び出すとします。

内訳:

  • 別々のビジネスロジッククラス(ビジネス層)へ。 EX:Bussiness1.cs
  • UI関連コードのクラスからパブリックデリゲートを公開します。
  • Form.cs(UIレイヤー)内のビジネスクラスのデリゲート値(この場合はプログレスバー更新)を設定します。
  • 必要に応じて、クラス(Business1.cs)はデリゲートを呼び出す必要があります。 (UI層で実装)
  • UIスレッドセーフ:D

知らんこれはあなたのためにあまりにも進んでいるが、これは本当にベストプラクティスであなたのコードを助ける場合。

1

PostIncident()は別のスレッドで実行されているため、フォームの値を更新するためのアクセス権がありません。これは、UIスレッドからのみ行うことができます。

あり、この周りにいくつかの方法がありますが、標準的な方法は、この記事に記載されています。

How to: Make Thread-Safe Calls to Windows Forms Control

Googleや詳細については、「InvokeRequired」のためにStackOverflowのを検索します。

関連する問題