2

在我的应用程序中,我有一个图片框和 2 个按钮(“是”和“否”)。Yes 将 1 添加到结果列表中,No 添加 0 并且都转到下一张图片。现在我需要在应用程序中实现一个计时器,如果没有提供答案,它会使图片转到下一个。我想为此使用后台工作人员。

当我不单击按钮时,下面的代码可以很好地切换图片。单击按钮会冻结 UI,因为后台工作人员保持“忙碌”状态。我确实知道 CancelAsync 不会立即停止后台工作程序,但实际上会触发 DoWork 中的返回语句。

所以我的问题是为什么后台工作人员一直很忙,或者我在这里完全走错了路?

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        Counter = 0;

        _backgroundWorker = new BackgroundWorker();
        _backgroundWorker.DoWork += _backgroundWorker_DoWork;
        _backgroundWorker.WorkerSupportsCancellation = true;
        _backgroundWorker.RunWorkerAsync();
    }

    private void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker bgw = sender as BackgroundWorker;

        GoToNextItem(); //Show next picture
        while (!bgw.CancellationPending) 
        {
            _getNext = false;
            Stopwatch sw = Stopwatch.StartNew();

            //Wait interval-time
            while (!_getNext)
            {
                 if ((sw.ElapsedMilliseconds > Test.Interval * 1000) && !bgw.CancellationPending)
                {
                    _getNext = true;
                }
                if (bgw.CancellationPending)
                {
                    e.Cancel = true;
                    return; //Breakpoint is hit here
                }
            }
            if (_getNext)
            {
               Result.Add(0);
                GoToNextItem();
            }
        }
        e.Cancel = true;
    }

    private void btnNo_Click(object sender, EventArgs e)
    {
        _backgroundWorker.CancelAsync();
        Result.Add(0);

        while (_backgroundWorker.IsBusy)
        {
        _backgroundWorker.CancelAsync();
            System.Threading.Thread.Sleep(20);
        }
        _backgroundWorker.RunWorkerAsync();
    }

    private void btnYes_Click(object sender, EventArgs e)
    {
        _backgroundWorker.CancelAsync();
        Result.Add(1);

        while (_backgroundWorker.IsBusy) //Stays busy ==> UI freezes here
        {
            _backgroundWorker.CancelAsync();
            System.Threading.Thread.Sleep(20);
        }
        _backgroundWorker.RunWorkerAsync();
    }

编辑

按照@Servy 的建议,使用计时器更改了代码。有关 backgroundworker-question 的更多详细信息,请阅读已接受答案的评论。

4

3 回答 3

3

你应该只使用 a System.Windows.Forms.Timer

private System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
public Form1()
{
    InitializeComponent();

    timer.Interval = 5000;
    timer.Tick += timer_Tick;
    timer.Start();
}

private void timer_Tick(object sender, EventArgs e)
{
    //runs in UI thread; code to go to next picture goes here
}

private void btnYes_Click(object sender, EventArgs e)
{
    timer.Start();
}
private void btnNo_Click(object sender, EventArgs e)
{
    timer.Start();
}

您还需要调用Start“是”和“否”按钮,因为它会重置计时器,这样您就可以返回到下一张图片的倒计时开始。

您可以Stop随时调用它来停止射击。

至于为什么您当前的代码会冻结 UI,这是因为您的 click 事件处理程序在 UI 线程中运行,并且它们Sleep在等待后台工作人员时正在调用。我不建议尝试修复这种方法,您应该使用计时器,但如果您确实想要,您需要将事件处理程序附加到后台工作人员的已完成/已取消事件并执行您当前正在执行的所有操作在那些其他处理程序中“等待 BGW 准备好”,而不是在 UI 线程中等待。

于 2012-10-09T16:13:00.047 回答
1

当您单击按钮时,您是在告诉后台工作人员取消,但您也立即开始线程睡眠,这基本上没有给后台工作人员足够的时间来有效地取消。

我测试了您的方法并添加了Application.DoEvents()如下一行:

while (_backgroundWorker.IsBusy)
{
    _backgroundWorker.CancelAsync();
    Application.DoEvents();
    System.Threading.Thread.Sleep(20);
}

这允许后台工作人员更快地取消,但我相信Application.DoEvents()以这种方式使用是一种代码味道。我建议使用@Servy 建议的方法。

于 2012-10-09T16:21:12.120 回答
1

我觉得你使用后台工作人员的方式是错误的..你应该做一些应该在后台运行而不会让 UI 挂起的事情..

但在你的情况下没有这样的要求..

只需使用计时器控件来执行相同操作,并在 GoToNextItem 方法中禁用“是/否”按钮并在最后重新启用..

于 2012-10-09T16:27:36.363 回答