3

私は2つのクラスを持っていAppleますDog. これらのクラスには両方とも、起動時に呼び出されるメソッドがDoLotsOfWork()あり、イベントを発行しますProgressChanged

public class Apple
{
    //For progress bars
    public event EventHandler<ProgressChangedEventArgs> ProgressChanged;
    private void OnProgressChanged(double progress)
    {
        if(ProgressChanged != null)
            ProgressChanged(this, new ProgressChangedEventArgs((int)(progress * 100), null));
    }

    //Takes a long time to run
    public void DoLotsOfWork()
    {
        for(lots of things)
        {
            ...
            OnProgressChanged(percentage);
        }
    }
}
//Dog is similar

UI がロックアップしないようにするために、BackgroundWorker. ラベルと進行状況バーを更新するために、(イベントを呼び出す)Apple.ProgressChangedDog.ProgressChanged呼び出しBackgroundWorker.ReportProgress て、BackgroundWorker.ProgressChanged何が起こっているかをユーザーに知らせます。

public class MainForm : Form
{
    private Apple _apple;
    private Dog _dog;
    private bool _isAppleCompleted;

    ...

    //Set the ProgressChanged callbacks and start the BackgroundWorker
    private void MainForm_Load(object sender, EventArgs e)
    {
        _apple.ProgressChanged += (a, args) => backgroundWorker1.ReportProgress(args.ProgressPercentage);
        _dog.ProgressChanged += (a, args) => backgroundWorker1.ReportProgress(args.ProgressPercentage);
        backgroundWorker1.RunWorkerAsync();
    }

    //Invoke the UI thread to set the status/progress
    private void SetStatus(string status)
    {
        lblStatus.Invoke((Action)(() => lblStatus.Text = status));
    }
    private void SetProgress(int progress)
    {
        progressBar.Invoke((Action)(() => progressBar.Value = progress));
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        _isAppleCompleted = false;
        SetStatus("Apple (Step 1/2)");
        _apple.DoLotsOfWork();

        //Thread.Sleep(1); //This *sometimes* fixes the problem!?

        _isAppleCompleted = true;
        SetStatus("Dog (Step 2/2)");
        _dog.DoLotsOfWork();
    }

    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        //_apple.DoLotsOfWork should cause the progress bar to go from 0 to 50
        //_dog.DoLotsOfWork should cause the progress bar to go from 50 to 100
        int progress = (_isAppleCompleted ? 50 : 0) + e.ProgressPercentage/2;
        SetProgress(progress);
    }

    private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        //stuff
    }
}

予想されること:プログレス バーが 0% から 50% に移動すると、「Apple (Step 1/2)」というテキストが表示されます。その後、進行状況バーが 50% から 100% に移動すると、「Dog (Step 2/2)」というフレーズが表示されます。

実際に何が起こるか:テキスト "Dog (Step 2/2)" だけが表示されます。プログレス バーは 0% から 100% になり、その後 50% に戻り、100% まで移動します。


イベントハンドラーはイベント呼び出し元と同じスレッドで実行されると思っていたので。すべてが本質的に同期 に行われているため、競合状態がどのように発生するかわかりません Control.Invoke()なぜこれが起こるのか、そしてそれを修正する方法を知っている人はいますか?Action

そして、はい、私はそれをチェックしました0 <= e.ProgressPercentage <= 100、そしてprogressBar.Maximum = 100

4

2 に答える 2

1

下部の仮定は正しいですが、コントロールが作成されたスレッドで BackgroundWorker が ProgressChanged を発生させることに注意する必要があるため、ワーカー スレッドから backgroundWorker1.ReportProgress を呼び出すと、UI スレッドで ProgressChanged ハンドラーが呼び出されます (それがバックグラウンドワーカーを作成した場所)、すべてが同期的に行われているわけではありません。また、プライベートな SetProgress メソッドでの Invoke が不要であることも意味します。

于 2012-05-10T11:05:47.667 に答える
0

バックグラウンド ワーカーで Invoke() メソッドを呼び出すと、バックグラウンド ワーカーを持つ目的が無効になります。進行状況を更新するには: 1. BackgroundWorker は ReportProgress メソッドを呼び出す必要があります。
2. WinForm の BackgroundWorker コントロールで、進行状況を更新するために ProgressChanged ハンドラーを追加します。

于 2012-05-09T17:15:51.900 に答える