【问题标题】:Release build runs differently than Debug build发布版本的运行方式与调试版本不同
【发布时间】:2016-12-02 22:22:55
【问题描述】:

对不起,我很困惑。我们有一个向一个硬件发送命令的过程,这个过程不堪重负。我创建了一个简单的解决方案,每发送 100 次后,它会暂停 1 秒,然后继续处理。在调试模式下运行,这完全解决了我们遇到的所有问题。但是,当我将此解决方案编译为 Release 版本时,我的计时器方法似乎永远卡住了。

在下面的代码中,我有一个简单的 while 循环,循环直到 bool 为真。 (我不想使用睡眠,因为我不希望线程变得无响应)

foreach (DataRow row in ds.Tables[0].Rows)
{
    string Badge = Database.GetString(row, "Badge");
    if (Badge.Length > 0)
    {
        if(Count < Controller.MaximumBadges)
        {
            if (processed == 100) // Every 100 downloads, pause for a second
            {
                processed = 0;
                StartTimer();
                while (!isWaitOver)
                {
                }
                Controller.PostRecordsDownloadedOf("Badges", Count);
            }

            if (Download(Badge, false))
            {
                Count++;
                processed++;
            }
        }
        else
            Discarded++;
    }
    TotalCount++;
}

private void StartTimer()
{
    // Create a timer with a one second interval.
    aTimer = new System.Timers.Timer(1000);
    // Hook up the Elapsed event for the timer. 
    aTimer.Elapsed += OnTimedEvent;
    aTimer.AutoReset = true;
    aTimer.Enabled = true;
    isWaitOver = false;
}

private void OnTimedEvent(Object source, System.Timers.ElapsedEventArgs e)
{
    isWaitOver = true;
    aTimer.Enabled = false;
}

任何人都可以看到在 Release 模式下运行时 while 循环无限卡住的原因吗?另外,如果有人对此有更好的解决方案,请告诉我。不过我必须使用 VS 2010。

感谢阅读。

【问题讨论】:

标签: c# visual-studio-2010


【解决方案1】:

您的代码中似乎存在竞争条件。启动计时器首先启用计时器,然后将isWaitOver 设置为falseOnTimedEvent 在运行时会将isWaitOver 设置为true。这有点不太可能,但在繁忙的系统上,计时器可能会在主线程将isWaitOver 设置为false 之前触发OnTimedEvent。如果发生这种情况,那么isWaitOver 可能总是以错误的形式出现在您的循环中。为防止这种情况发生,请将您的 isWaitOver = false 行放在 aTimer.Enabled = true 之前。

更可能的问题是优化器对代码中的内容进行重新排序。如果单个线程不会注意到差异,则允许这样做,但它可能会在像这样的多线程场景中导致问题。要解决此问题,您可以制作 isWaitOver volatile 或将 memory barriers 放入您的代码中。请参阅Threading in C# by Joseph Albahari 以获得良好的写作。

一般来说,虽然当它达到易失性和内存屏障产生影响的地步时,您已经使您的代码变得复杂和脆弱。内存屏障是非常高级的东西,极容易出错并且几乎不可能正确测试(例如,行为取决于您使用的 CPU 型号)。我的建议是将isWaitOver 切换为ManualResetEvent 并等待它得到计时器线程的信号。这具有防止您的代码进入 CPU 占用自旋循环的额外优势。

最后,您的代码有句柄泄漏。每次您都在创建一个新的 Timer 对象,但您永远不会再次处理它。你可以像我展示的那样在创建一个新的之前处理它,或者只使用一个而不继续重新创建它。

    ManualResetEvent isWaitOver = new ManualResetEvent(false);

    private void Run()
    {
        foreach (DataRow row in ds.Tables[0].Rows)
        {
            string Badge = Database.GetString(row, "Badge");
            if (Badge.Length > 0)
            {
                if (Count < Controller.MaximumBadges)
                {
                    if (processed == 100) // Every 100 downloads, pause for a second
                    {
                        processed = 0;
                        StartTimer();
                        isWaitOver.WaitOne();
                        Controller.PostRecordsDownloadedOf("Badges", Count);
                    }

                    if (Download(Badge, false))
                    {
                        Count++;
                        processed++;
                    }
                }
                else
                    Discarded++;
            }
            TotalCount++;
        }
    }

    private void StartTimer()
    {
        // Create a timer with a one second interval.
        if (aTimer != null) aTimer.Dispose();
        aTimer = new System.Timers.Timer(1000);
        // Hook up the Elapsed event for the timer.
        isWaitOver.Reset();
        aTimer.Elapsed += OnTimedEvent;
        aTimer.AutoReset = true;
        aTimer.Enabled = true;
    }

    private void OnTimedEvent(Object source, System.Timers.ElapsedEventArgs e)
    {
        aTimer.Enabled = false;
        isWaitOver.Set();
    }

【讨论】:

  • 你修好了!非常棒!也感谢您的详细解释。如果我以后需要这样的修复,我会记住这个解决方案。
猜你喜欢
  • 2018-09-08
  • 1970-01-01
  • 2010-10-27
  • 2020-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多