c# - 使用事件和 BackgroundWorker 更新导致竞争条件的 UI

标签 c# winforms multithreading thread-safety backgroundworker

我有两个类(class),AppleDog .这两个类都有一个在启动时调用的方法,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(步骤 1/2)”。然后随着进度条从 50% 移动到 100%,将显示短语“Dog (Step 2/2)”。

实际情况:只显示文本“Dog (Step 2/2)”。进度条从 0% 到 100%,然后回到 50% 并向上移动到 100%。


因为我认为 事件处理程序与事件调用者在同一线程上运行;我 Control.Invoke() block 直到 Action完成后,我不明白怎么会有竞争条件,因为一切基本上都是同步发生的。 有没有人知道为什么会发生这种情况,以及如何解决它?

是的,我检查了 0 <= e.ProgressPercentage <= 100 , 和 progressBar.Maximum = 100 .

最佳答案

您在底部的假设是正确的,但您应该注意到 BackgroundWorker 将在创建控件的线程上引发 ProgressChanged,因此从工作线程调用 backgroundWorker1.ReportProgress 会调用 UI 线程上的 ProgressChanged 处理程序(假设那是你创建后台 worker 的地方),所以并不是所有事情都是同步发生的。这也意味着您的私有(private) SetProgress 方法中的 Invoke 是不必要的。

关于c# - 使用事件和 BackgroundWorker 更新导致竞争条件的 UI,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10520524/

相关文章:

c# - 以编程方式创建 GroupBox 并在其中添加标签

.net - 我应该选择 N 层架构而不是 MVC 吗?

C# 同时更新两个文本框?

java - 同步类的成员变量

c# - 序列化类时未标记为可序列化错误

c# - 使用 Action 字典而不是 switch 语句

c# - 我应该将 Dispose 逻辑分离到一个部分类文件中吗?

java - 通过 Twitter4J 进行多线程 Twitter 访问

multithreading - Delphi DLL - 线程安全

c# linq GroupBy 和仅总结每组中的最低值