【问题标题】:Odd behavior using BackgroundWorker with Thread.Sleep使用带有 Thread.Sleep 的 BackgroundWorker 的奇怪行为
【发布时间】:2015-01-28 17:16:37
【问题描述】:

我以前从未见过这样的事情。我正在使用 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收到了第二个任务!?

【问题讨论】:

  • 你是如何设置BackgroundWorker的?
  • 听起来你有不止一个后台工作人员在运行,当你跳过 Sleep 调用时,调试器会在两者之间切换。
  • 您的代码不是线程安全的,ProgressChanged 事件处理程序在您的工作人员调用 ReportProgress() 之后 运行任意时间。这意味着您的事件处理程序不能使用 clockThen/Now 变量。它不必使用 ReportProgress(int, object) 重载,而是允许您将值传递给事件处理程序。
  • 添加了 BackgroundWorker 初始化代码。 @Hans 等等,你的意思是 ReportProgress() 没有按顺序处理?我认为我的代码逻辑本质上是线程安全的,因为这些变量仅在 ReportProcess() 启动后才被读取,这意味着当时它们没有被访问。如果是这样,那将是我的错误。

标签: c# .net multithreading backgroundworker


【解决方案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 的值。

【讨论】:

  • 这可行,但就像 Hans Passant 所说,它不一定是线程安全的或以受控顺序执行。我认为 BackgroundWorker_DoWork 在 BackgroundWorker_ReportProgress 执行、完成并返回到 DoWork 部分时暂时暂停。显然情况并非如此。 DoWork 继续异步执行。它继续运行它的无限循环,这解释了为什么分配顺序很快就会失控。没有幻像断点,循环只是在 ReportProgress 函数甚至到达/执行代码之前重新开始。
  • @RagingCain 我没有在我的回答中解决任何问题,我只是解释出了什么问题,你可以随心所欲地解决问题。
  • @ScottChamerlain 对不起,我之前读它的时候有点匆忙。感谢您提供额外的信息。
猜你喜欢
  • 1970-01-01
  • 2014-04-21
  • 2011-03-16
  • 2011-08-07
  • 1970-01-01
  • 2012-11-12
  • 2019-08-19
  • 2012-01-10
  • 2016-04-30
相关资源
最近更新 更多