Jonathan Hansen Jonathan Hansen - 7 days ago 7
C# Question

Release build runs differently than Debug build

Sorry, but I'm perplexed. We have a process that sends commands to a piece of hardware, that gets overwhelmed. I created a simple solution that after every 100 sends, it pauses for 1 second, before continuing with processing. Running in Debug mode, this completely solved all the issues we were experiencing. However, when I compile this solution into a Release build, my timer method seemingly becomes stuck spinning forever.

In the below code, I have a simple while loop, that loops until the bool is true. (I didn't want to use sleep, because I don't want the thread becoming unresponsive)

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;
}


Can anybody see a reason the while loop would infinitely be stuck when running in Release mode? Also, if anybody sees a better solution to this, please let me know. I have to use VS 2010 though.

Thanks for reading.

Answer

Your code appears to have a race condition in it. Start timer first enables the timer then sets isWaitOver to false. The OnTimedEvent when it runs then sets isWaitOver to true. It is a bit unlikely, but on a busy system it may be possible for the timer to fire OnTimedEvent before the main thread gets to setting isWaitOver to false. If this occurs then isWaitOver may always end up appearing to your loop as false. To prevent this put your isWaitOver = false line before aTimer.Enabled = true.

The more likely issue is to do with the optimizer reordering things in your code. It is allowed to do this if a single thread would not notice the difference but it can cause issues in multi-threaded scenarios like this. To resolve this you can either make isWaitOver [volatile][1] or put memory barriers in your code. See Threading in C# by Joseph Albahari for a good write up.

Generally though when it gets to the point where volatile and memory barriers are making a difference you have already made your code way to complex and fragile. Memory Barriers are very advanced things that are extremely easy to get wrong and near impossible to test correctly (for instance the behaviour varies depending on the CPU model you are using). My advice would be to switch isWaitOver for a [ManualResetEvent][3] and waiting on it to get signalled by the timer thread. This has the added advantage of preventing your code going into a CPU hogging spin loop.

Finally your code has a handle leak. Each time around you are creating a new Timer object, but you are never disposing it again. You can either dispose it before creating a new one as I've shown or simply use one and not keep recreating it.

    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();
    }