2016-12-14 6 views
3

У меня есть блок кода, который читается из очереди, обрабатывает один элемент (в собственном потоке), а затем повторяется до тех пор, пока очередь не будет пуста.Не могу понять, почему я получаю исключение NullReferenceException

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() либо удаляет элемент из очереди, или возвращает нулевое значение, если очередь пуста. GetOrders() обрабатывает объект.

Проблема в том, что иногда код вызывает исключение NullReferenceException в этой строке Task.Run (() => GetOrders (sync.SyncId, customerId));

В отладчике синхронизация null (причина для исключения), но customerId имеет значение. Это говорит мне, что синхронизация имела значение в предыдущей строке. Это меня сбивает с толку, я думаю, что это имеет какое-то отношение к Task.Run и threading, но я не понимаю, как локально ограниченная переменная спонтанно меняет свою ценность.

+0

Если синхронизация = _common.StartSync() аннулирует ваш объект синхронизации до и из GetOrders() заканчивает, его имеет смысл, почему он это сделает. Можете ли вы сделать GetOrders и GetOrdersAsync() реальными методами async, а затем ждать GetOrders? Затем он будет ждать, прежде чем пытаться обработать следующую синхронизацию. – Dispersia

ответ

6

Вы обновляете ссылку sync, прежде чем ваша задача получит возможность работать с ней. Обратите внимание, что задачи не обязательно начинаются немедленно. В некоторых случаях, ваша задача может начаться после того, как следующий выполняются дальше:

sync = _common.StartSync(); 

Теперь ваша ссылка на sync потенциально пустой, и когда ваша задача выходит доступ к sync.SyncId, вы получите нулевое ссылочное исключение.

Измените код следующим образом:

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

Это работает, потому что мы просто хотим, чтобы пройти в Id. Что делать, если вы хотите пройти в самом объекте? Вы должны были бы создать новую переменную, которая будет не быть изменен за пределами закрытия:

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

Для вашего последнего примера это мелкая копия. Изменение синхронизации также изменит captureSync. Ему нужно будет сделать глубокую копию, в которой я просто буду придерживаться вашего первого примера. – Dispersia

+2

@Dispersia «Изменение синхронизации также изменит captureSync» ... и вы абсолютно уверены в этом? – spender

+0

@spender да? это то, что делает мелкая копия. Попробуйте сами, создайте объект, назначьте его (это сделает это по ссылке), затем измените новый объект и посмотрите исходное изменение. – Dispersia