James Sullivan
James Sullivan

Reputation: 131

Cannot understand why I am getting a NullReferenceException

I have a block of code that is reading from a queue, processing one item (in its own thread), and then repeating until the queue is empty.

public ActionResult GetOrdersAsync() {

        int count = 0;
        SyncDM sync = _common.StartSync();

        while (sync != null && sync.SyncId != 0) {

            int customerId;
            bool result = int.TryParse(sync.Payload, out customerId);
            if (result) {
                Task.Run(() => GetOrders(sync.SyncId, customerId));
            }

            count++;
            //Process the next Sync
            sync = _common.StartSync();

        }

        return Json(new JsonModel {
            Message = "Started " + count + " instances of GetOrders",
            Success = count > 0
        });

    }

StartSync() either removes an item from the queue, or returns null if the queue is empty. GetOrders() processes the object.

The problem is that sometimes the code raises a NullReferenceException on this line Task.Run(() => GetOrders(sync.SyncId, customerId));

In the debugger, Sync is null (reason for exception), but customerId has a value. This tells me sync had a value on the previous line. This is confusing me, I'm thinking it has something to do with Task.Run and threading but I don't understand how a locally scoped variable is spontaneously changing its value.

Upvotes: 3

Views: 103

Answers (1)

Rob
Rob

Reputation: 27367

You're updating the reference of sync before your task gets a chance to operate on it. Note that tasks don't necessarily start immediately. In some cases, your task may start after the following is executed further down:

sync = _common.StartSync();

Now your reference to sync is potentially null, and when your task goes to access sync.SyncId, you get a null reference exception.

Change your code to the following:

if (result) {
    var syncId = sync.SyncId;
    Task.Run(() => GetOrders(syncId, customerId));
}

This works because we just want to pass in the Id. What if you wanted to pass in the object itself? You'd need to create a new variable which will not be modified outside of the closure:

if (result) {
    var capturedSync = sync;
    //Assuming GetOrders now takes a `Sync`
    Task.Run(() => GetOrders(capturedSync, customerId));
}

Upvotes: 6

Related Questions