简体   繁体   中英

If statement evaluates to false but still branches as if it was true

I am quite stumped. In an async method, I have a few initial guard statements, that throw exceptions if specific conditions are met.

One of them is the following:

var txPagesCount = _transactionPages.Count;
if (txPagesCount == 0)
    throw new InvalidOperationException(string.Format("Cannot commit transaction {0}. It is empty.", _txId));

This is supposed to ensure there are pages in the _transactionPages dictionary and throw if there are none.

This is what happens when I run it (release and debug build, debugger attached):

页数为3

So the number of pages in the dictionary is 3.

if语句的计算结果为false

And so, as expected, the if statement comparing 3 to 0 evaluates to false.

But then, when stepping further:

进入分支的步骤

It steps into the branch as if the if statement evaluated to true, and throws the exception.

What am I missing here?

UPDATE

When I do this:

private static readonly object _globalLock = new object();

public async Task<Checkpoint> CommitAsync(PageNumber dataRoot, PageNumber firstUnusedPage)
{
    lock (_globalLock)
    {
        if (IsCompleted)
            throw new InvalidOperationException(string.Format("Cannot commit completed transaction {0}", _txId));
        var txPagesCount = _transactionPages.Count;
        if (txPagesCount == 0)
            throw new InvalidOperationException(string.Format("Cannot commit transaction {0}. It is empty.", _txId));
    }

the if statement does not branch to throw the exception. This is the case for both debug and release build. Is something messing up the call stack? Also, if instead of the lock I add System.Threading.Thread.MemoryBarrier(); after the if statement, it will not go into the branch.

UPDATE 2

The mystery becomes a bit larger. It is almost as if c++ scoping rules are used :D The code below ( in debug build ) will show the expected behavior: not go into the branch and not throw. In release build , it will go into the branch and throw just as before.

private static readonly object _globalLock = new object();

public async Task<Checkpoint> CommitAsync(PageNumber dataRoot, PageNumber firstUnusedPage)
{
    //lock (_globalLock)
    {
        if (IsCompleted)
            throw new InvalidOperationException(string.Format("Cannot commit completed transaction {0}", _txId));
        var txPagesCount = _transactionPages.Count;
        if (txPagesCount == 0)
            throw new InvalidOperationException(string.Format("Cannot commit transaction {0}. It is empty.", _txId));
    }

If I comment out the "scoping braces" it will go into the branch and throw the exception (as in my original images).

FINAL? UPDATE

Well that sucks. I made a few changes to unrelated areas of code and now I am no longer able to reproduce the problem.

Wow, asynchronous debugging.

I only ever found one way to do this effectively. Use tracing (hopefully something decent, maybe log4net). Make sure to output timestamp and threadId on every single line. (also support synchronization of output per call to trace library for when you need it which doesn't have to be on all the time)

That's it really.

Note: Simply writing to a file or stdout works for a first blush, but if you start using it more, get a library.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM