使用BackgroundWorker和Thread.Sleep的奇怪行为

时间:2015-01-28 17:16:38

标签: c# .net multithreading backgroundworker

我以前从未见过这样的事情。我正在使用Visual Studio 2015预览并在Visual Studio 2012上验证它。

    private void InitializeBackgroundWorkers()
    {
        MONITOR.WorkerReportsProgress = true;
        MONITOR.WorkerSupportsCancellation = true;
        MONITOR.DoWork += new DoWorkEventHandler(MONITOR_DoWork);
        MONITOR.ProgressChanged += new ProgressChangedEventHandler(MONITOR_ProgressChanged);
        MONITOR.RunWorkerCompleted += new RunWorkerCompletedEventHandler(MONITOR_WorkCompleted);

        MONITOR2.WorkerReportsProgress = true;
        MONITOR2.WorkerSupportsCancellation = true;
        MONITOR2.DoWork += new DoWorkEventHandler(MONITOR2_DoWork);
        MONITOR2.ProgressChanged += new ProgressChangedEventHandler(MONITOR2_ProgressChanged);
    }

问题出现在BackgroundWorker的DoWork事件中。显然我不知何故犯了错误。

    private void MONITOR2_DoWork(object sender, DoWorkEventArgs e)
    {
        while (CONTINUE)
        {
            oldClockNow = clockNow;
            //BP1
            clockThen = RunTime.ElapsedTicks;
            Thread.Sleep(1000);
            //BP2
            clockNow = RunTime.ElapsedTicks;

            TS = RunTime.Elapsed;

            MONITOR2.ReportProgress(0);
        }

        RunTime.Stop();
    }

问题是因为各种原因我注意到ClockThen总是"更新"比ClockNow。到目前为止,我忽略了它,并改变了我的代码,将ClockThen用作ClockNow,反之亦然。

我终于有时间用断点查看代码了。我在clockThen赋值时放置一个,在clockNow赋值时放置一个,在ReportProgress事件中放置一个,所以我可以在MONITOR2堆栈之外再停一次。

注意:我使用ProgressChanged作为更新主UI的方法。

    private void MONITOR2_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        double calcClockOld = (((double)oldClockNow) * (1F / Stopwatch.Frequency));
        double calcClockThen = (((double)clockThen) * (1F / Stopwatch.Frequency));
        double calcClockNow = (((double)clockNow) * (1F / Stopwatch.Frequency));

        //BP3
        tbxClockDrift.Text = ((calcClockThen - calcClockNow) * 1000000).ToString("0.#####\\ µs");

        string timestamp = String.Format("{0:00}:{1:00}:{2:00}", TS.Hours, TS.Minutes, TS.Seconds);

        tbxTime.Text = DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToLongTimeString();
        tbxRunTime.Text = timestamp;
        tbxTimeZone.Text = TimeZone.CurrentTimeZone.ToUniversalTime(DateTime.Now).ToString();
    }

第三个断点位于tbxClockDrift.Text赋值。

所以我总共有3个断点,但是当逐步执行代码时,BP1总是执行两次,我相信它为什么价值会更高!

所以我逐步看到这是一个幻影断点:

Step Through 1,clockThen = RunTime.ElapsedTicks;

Step Through 2,跳转到clockNow = Runtime.ElapsedTicks;睡了一会后。

单步执行3,返回时钟然后分配(验证新值!)

Step through 4,tbxClockDrift.Text赋值!

为什么clockThen接收第二个作业!?

1 个答案:

答案 0 :(得分:1)

如果我稍微重写你的方法以使睡眠成为在while循环中发生的最后一件事,问题会更加明显。此代码的行为与您当前的代码完全相同。

private void MONITOR2_DoWork(object sender, DoWorkEventArgs e)
{
    if(CONTINUE)
    {
        oldClockNow = clockNow;
        //BP1
        clockThen = RunTime.ElapsedTicks;
        Thread.Sleep(1000);
        while (true)
        {
            //BP2
            clockNow = RunTime.ElapsedTicks;

            TS = RunTime.Elapsed;

            MONITOR2.ReportProgress(0);
            if(!CONTINUE)
                break;

            oldClockNow = clockNow;
            //BP1
            clockThen = RunTime.ElapsedTicks;
            Thread.Sleep(1000);
        }
    }
    RunTime.Stop();
}

所以clockThen没有收到第二个作业只是clockThen是你睡觉前做的最后一件事。此外,ReportProgress的处理不一定要在你调用函数时发生,调用函数只是将事件排队,以便在下一次UI可以解决它时触发。

所以发生的事情是你分配clockNow,排队进度报告,你给clockThen分配一个值,最后你开始睡觉。当你正在睡觉时ReportProgress终于开始跑步了,你得到了你在睡觉之前分配的clockThen的价值。