NullReference when using Task.WaitAll - c#

I have a Windows service that spawns objects that do work. The objects have two methods that get kicked off as Tasks. During housekeeping or shutdown, I call a Stop method on the object that tell the Tasks to stop. I have a list of the Tasks created for the objects' methods, so I do a Task.WaitAll for them. But, I'm getting a NullReferenceException during shutdown. I thought one of the Task objects was null, so I tested for it with a .Where(pt => pt != null).ToArray(), but that didn't work.
Here's a snippet:
var peProcessor = new PrintExpertProcessor(runId);
processorTasks.Add(Task.Factory.StartNew(() => peProcessor.ProcessRun()));
processorTasks.Add(Task.Factory.StartNew(() => peProcessor.StartMonitor()));
processors.Add(peProcessor);
// Later in the code
Task.WaitAll(processorTasks.ToArray()); // System.AggregateException: System.NullReferenceException:
I think that the Task is not null, but that the original processor object is null. But, I'm not sure how to check that or prevent this. I'm new to Tasks, so I'm still getting my head around it.
Thoughts?
Full Error Message:
System.AggregateException: System.NullReferenceException: Object reference not set to an instance of an object.
at WOW.PrintExpert.AwdProcessor.Code.PrintExpertProcessor.StopProcessor()
at WOW.PrintExpert.AwdProcessor.Code.PrintExpertProcessor.ProcessRun()
at System.Threading.Tasks.Task.Execute()
--- End of inner exception stack trace ---
at System.Threading.Tasks.Task.WaitAll(Task[] tasks, Int32 millisecondsTimeout, CancellationToken cancellationToken)
at WOW.PrintExpert.AwdProcessorService.ProcessorService.OnStop()
---> (Inner Exception #0) System.NullReferenceException: Object reference not set to an instance of an object.
at WOW.PrintExpert.AwdProcessor.Code.PrintExpertProcessor.StopProcessor()
at WOW.PrintExpert.AwdProcessor.Code.PrintExpertProcessor.ProcessRun()
at System.Threading.Tasks.Task.Execute()<---

As explained at the documentation linked by DJ KRAZE, the AggregateException means that the NullReferenceException was thrown by one of the tasks (not that the processorTasks collection is null, nor one of its items, as that would produce an ArgumentNullException)
Check the callstack of the NullReferenceException (which would be found at the AggregateException.InnerExceptions property), or simply set your debugger to break on thrown NullReferenceExceptions, in order to find the actual exception.

Ok, here's what I went with. It seems to be working as I want and it is testing well.
// Service class
private Dictionary<int, Task> processorMonitorTasks = new Dictionary<int, Task>();
private Dictionary<int, Task> processorTasks = new Dictionary<int, Task>();
When new work is received:
// Spawn new processor object
var peProcessor = new PrintExpertProcessor(runId);
// Create tasks for the processors methods
processorTasks.Add(runId, Task.Run(() => peProcessor.ProcessRun()));
processorMonitorTasks.Add(runId, Task.Run(() => peProcessor.StartMonitor()));
// Add the processor the collection
processors.Add(peProcessor);
When the service shuts down, it does this:
// Copying the object references to a new list prevents enumeration changed exceptions.
foreach (var proc in processors.ToList())
{
if (proc != null && !proc.IsStopped)
{
// Direct that each object come to a stop
proc.StopProcessor();
}
}
// Now, wait for each one to stop.
Task.WaitAll(processorTasks.Values.ToArray());
Task.WaitAll(processorMonitorTasks.Values.ToArray());

Related

What could cause ContinuationTaskFromResultTask`1[TAntecedentResult].InnerInvoke() to throw a NullReferenceException? [duplicate]

For the following piece of code (.NET v4.0.30319) I am getting a null reference exception indicated below in the second continuation.
Most interestingly this issue has only occurred in machines with 8GB RAM but other users have 16GB and more and they haven't reported any issue, and it is a very intermittent issue which leads me to suspect a garbage collection issue.
The GetData() can be called multiple times so the first continuation of _businessObjectTask will only be called once as _businessObjects will have been populated from that point forward.
I imagine the Object reference not set to an instance of an object exception is being thrown because either
_businessObjectTask is null and it can't continue from a null task.
items variable which is passed in as a parameter is null somehow
The line number in my log file (748) points to the one highlighted below and not the lambda expression which points to #1 above instead of #2. I've played around in Visual Studio and each of the lines following businessObjectTask.ContinueWith() are considered different i.e. if it was a null reference within the lambda expression it would give a different line number to 748
Any help would be greatly appreciated.
Edit:
This isn't related to What is a NullReferenceException, and how do I fix it? because that is a much more basic explanation of null reference whereas this is much more complicated and subtle.
Exception
Full details of the stack trace (edited for simplicity with dummy class and namespace names)
Object reference not set to an instance of an object.
at ApplicationNamespace.ClassName`1.<>c__DisplayClass4e.<GetData>b__44(Task`1 t) in e:\ClassName.cs:line 748
at System.Threading.Tasks.ContinuationTaskFromResultTask`1.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
Code
private static IDictionary<string, IBusinessObject> _businessObjects;
private Task<IDictionary<string, IBusinessObject>> _businessObjectTask;
public Task GetData(IList<TBusinessItem> items))
{
Log.Info("Starting GetData()");
if (_businessObjects == null)
{
var businessObjectService = ServiceLocator.Current.GetInstance<IBusinessObjectService>();
_businessObjectTask = businessObjectService.GetData(CancellationToken.None)
.ContinueWith
(
t =>
{
_businessObjects = t.Result.ToDictionary(e => e.ItemId);
return _businessObjects;
},
CancellationToken.None,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Current
);
}
var taskSetLEData = _businessObjectTask.ContinueWith // Line 748 in my code - "Object reference not set to an instance of an object." thrown here
(
task =>
{
items.ToList().ForEach
(
item =>
{
IBusinessObject businessObject;
_businessObjects.TryGetValue(item.Id, out businessObject);
item.BusinessObject = businessObject;
}
);
},
CancellationToken.None,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Default
);
}
Resolution:
So having used what I've learned from this question I went back to the original code and figured it all out.
Turns out the reason for this NRE was because the _businessObjectTask is non-static where as the _businessObjects is static.
This means that _businessObjects is null the first time GetData() is called which then sets _businessObjectTask to non-null. Then when _businessObjectTask.ContinueWith gets called it is non-null and continues without problem.
However if a second instance of this class above is instantiated, _businessObjects has already been populated so _businessObjectTask remains null. Then when _businessObjectTask.ContinueWith gets called, a NRE on _businessObjectTask is thrown.
There were a couple of options I could have taken but I ended up removing the _businessObjectTask to a synchronous method call which means that I dont need to use the continuation anymore and I set _businessObjects once.
This is a synchronization problem.
You are assuming that _businessObjectTask is always assigned before _businessObjects.
That is, however, not guaranteed. It is possible that your continuation that assign _businessObjects is executed inline and therefore before businessObjectService.GetData(...).ContinueWith(...) returns.
// This assignment could happend AFTER the inner assignment.
_businessObjectTask = businessObjectService.GetData(CancellationToken.None)
.ContinueWith
(
t =>
{
// This assignment could happen BEFORE the outer assignment.
_businessObjects = t.Result.ToDictionary(e => e.ItemId);
Therefore, it is possible that _businessObjects is not null although _businessObjectTask is null.
If a concurrent thread would enter your GetData method at that time it would clearly not enter
if (_businessObjects == null) // not entered because it's not null
{
...
}
...and instead continue with
var taskSetLEData = _businessObjectTask.ContinueWith // line 748
...which will cause a null reference exception since _businessObjectTask is null.
Here's how you could simplify your code and resolve this synchronization problem:
private Lazy<Task<IDictionary<string, IBusinessObject>>> _lazyTask =
new Lazy<Task<IDictionary<string, IBusinessObject>>>(FetchBusinessObjects);
private static async Task<IDictionary<string, IBusinessObject>> FetchBusinessObjects()
{
var businessObjectService = ServiceLocator.Current.GetInstance<IBusinessObjectService>();
return await businessObjectService.GetData(CancellationToken.None).ToDictionary(e => e.ItemId);
}
public async Task GetData(IList<TBusinessItem> items)
{
Log.Info("Starting GetData()");
var businessObjects = await _lazyTask.Value;
items.ToList().ForEach
(
item =>
{
IBusinessObject businessObject;
businessObjects.TryGetValue(item.Id, out businessObject);
item.BusinessObject = businessObject;
}
);
}
Notes:
Using Lazy<T> to ensure that the business object service is only invoked once (per instance of this class, whatever it is).
Using async/await to simplify code.
You may want to consider declaring _lazyTask as static. In your code there seem to be a mixup between static/non-static fields. I cannot know which is right for you.

throw original exception inside Parallel methods instead of aggregate exception

I have two CPU-intensive methods inside a Parallel.Invoke call:
Parallel.Invoke(
() => { GetMaxRateDict(tradeOffObj); },
() => { GetMinRateDict(tradeOffObj); }
);
For a MCVE, assume:
public void GetMaxRateDict(object junk)
{
throw new Exception("Max exception raised, do foo...");
}
public void GetMinRateDict(object moreJunk)
{
throw new Exception("Min exception raised, do bar...")
}
I throw different exceptions in each of these inner methods. However, if one of these gets thrown, the Parallel wrapper throws a more generic exception: "One or more errors occurred", which is specific enough to show in my UI layer.
Can I grab the original exception somehow and throw it instead?
I would like the Parallel task to stop entirely if possible to raise the inner exception, but if that's not possible, at least being able to raise it once the two methods complete is what I need. Thanks.
Can I grab the original exception somehow and throw it instead?
"It" implies that there will only be on exception. Even though that's probably true, because you're executing actions in parallel you can't 100% rule out the possibility that multiple actions throw exceptions even if you attempt to cancel the others after the first exception. If you're okay with that, we can go from the assumption that we only expect one exception and we're okay with only catching one. (If you allow the other invocation to continue after one throws an exception the possibility of having two exceptions increases.)
You can use a cancellation token. If one of the invocations below throws an exception, it should catch that exception, place it in a variable or queue, and then call
source.Cancel;
Doing so will cause the entire Parallel.Invoke to throw an OperationCanceledException. You can catch that exception, retrieve the exception that was set, and rethrow that.
I'm going to go with the other answer's suggestion of a ConcurrentQueue just as a matter of practice because I don't think we can rule out the remote possibility that a second thread could throw an exception before being canceled.
This started off seeming small, but eventually it got so involved that I separated it into its own class. This makes me question whether my approach is needlessly complex. The main intent was to keep the messy cancellation logic from polluting your GetMaxRateDict and GetMinRateDict methods.
In addition to keeping your original methods unpolluted and testable, this class is itself testable.
I suppose I'll find out from the other responses whether this is a decent approach or there's something much simpler. I can't say I'm particularly excited about this solution. I just thought it was interesting and wanted to write something that did what you asked.
public class ParallelInvokesMultipleInvocationsAndThrowsOneException //names are hard
{
public void InvokeActions(params Action[] actions)
{
using (CancellationTokenSource source = new CancellationTokenSource())
{
// The invocations can put their exceptions here.
var exceptions = new ConcurrentQueue<Exception>();
var wrappedActions = actions
.Select(action => new Action(() =>
InvokeAndCancelOthersOnException(action, source, exceptions)))
.ToArray();
try
{
Parallel.Invoke(new ParallelOptions{CancellationToken = source.Token},
wrappedActions)
}
// if any of the invocations throw an exception,
// the parallel invocation will get canceled and
// throw an OperationCanceledException;
catch (OperationCanceledException ex)
{
Exception invocationException;
if (exceptions.TryDequeue(out invocationException))
{
//rethrow however you wish.
throw new Exception(ex.Message, invocationException);
}
// You shouldn't reach this point, but if you do, throw something else.
// In the unlikely but possible event that you get more
// than one exception, you'll lose all but one.
}
}
}
private void InvokeAndCancelOthersOnException(Action action,
CancellationTokenSource cancellationTokenSource,
ConcurrentQueue<Exception> exceptions)
{
// Try to invoke the action. If it throws an exception,
// capture the exception and then cancel the entire Parallel.Invoke.
try
{
action.Invoke();
}
catch (Exception ex)
{
exceptions.Enqueue(ex);
cancellationTokenSource.Cancel();
}
}
}
The usage would then be
var thingThatInvokes = new ParallelInvokesMultipleInvocationsAndThrowsOneException();
thingThatInvokes.InvokeActions(
()=> GetMaxRateDict(tradeOffObj),
() => GetMinRateDict(tradeOffObj));
If it throws an exception, it will be a single exception from one invocation failure, not an aggregate exception.
Not quite sure whether given example would answer your question, but it might improve overall solution:
private static void ProcessDataInParallel(byte[] data)
{
// use ConcurrentQueue to enable safe enqueueing from multiple threads.
var exceptions = new ConcurrentQueue<Exception>();
// execute the complete loop and capture all exceptions
Parallel.ForEach(data, d =>
{
try
{
// something that might fail goes here...
}
// accumulate stuff, be patient ;)
catch (Exception e) { exceptions.Enqueue(e); }
});
// check whether something failed?..
if (exceptions.Count > 0) // do whatever you like ;
}
Such an approach gives additional freedom in terms of collecting different kinds of exceptions into different queues (if necessary) or re-throwing aggregated exception further (such that no sensitive info bubbled up or you may convey particular exception with a user-friendly description of possible reasons, etc.).
Generally, that is correct way of exception management with parallelization. Not only in C#.

Using tasks getting "Object reference not set to an instance of an object"

first of all, I already searched through SO in many NullReferenceException questions. here and here
I am getting an error "Object reference not set to an instance of an object" when I try to call Task.WaitAll(tasks);
I am sure that I am initializing the all the objects before trying to call methods. Below is the code snipet:
public IList<ResourceFreeBusyDto> GetResourceFreeBusy(int requesterId, int[] resourceIds, DateTime start, DateTime end)
{
IList<ResourceFreeBusyDto> result = new List<ResourceFreeBusyDto>();
ValidateFreeBusyInputs(resourceIds, start, end);
List<Task<IList<ResourceFreeBusyDto>>> tasks = new List<Task<IList<ResourceFreeBusyDto>>>();
TimeSpan timeout = new TimeSpan(0,0,30); // 30 seconds
// Split resources to persons and meetingRooms
List<int> personIds;
List<int> meetingRoomIds;
SplitResourceIds(resourceIds, out personIds, out meetingRoomIds);
// Go online for persons
if (personIds.Count > 0)
{
//result.AddRange(GetResourceFreeBusyOnline(requesterId, personIds.ToArray(), start, end)); // paralelizovat
Task<IList<ResourceFreeBusyDto>> task = Task.Factory.StartNew(() => GetResourceFreeBusyOnline(requesterId, personIds.ToArray(), start, end));
tasks.Add(task);
}
// Go online for meetingrooms if they are not cached in DB
if (meetingRoomIds.Count > 0)
{
DateTime? lastModifiedMeetingRoomFreeBusy = new DateTime();
lastModifiedMeetingRoomFreeBusy = freeBusyRepository.GetMinTimeStamp();
if (lastModifiedMeetingRoomFreeBusy.Value.AddMinutes(1) < DateTime.UtcNow || lastModifiedMeetingRoomFreeBusy == null)
{
//result.AddRange(GetResourceFreeBusyOnline(requesterId, meetingRoomIds.ToArray(), start, end)); // mrIDs
Task<IList<ResourceFreeBusyDto>> task = Task.Factory.StartNew(() => GetResourceFreeBusyOnline(requesterId, resourceIds, start, end));
tasks.Add(task);
}
else
{
//result.AddRange(GetMeetingRoomsFreeBusyCached(requesterId, meetingRoomIds.ToArray(), start, end)); // mrIDs
Task<IList<ResourceFreeBusyDto>> task = Task.Factory.StartNew(() => GetMeetingRoomsFreeBusyCached(requesterId, resourceIds, start, end));
tasks.Add(task);
}
}
bool status = false;
try
{
var a = tasks.ToArray();
Task.WaitAll(a);
status = Task.WaitAll(tasks.ToArray(), timeout);
}
catch (Exception ex)
{
Log.Fatal(ex);
}
if (status == false)
{
throw new ApplicationException(
string.Format("Timeout expired." +
" The timeout period elapsed prior to completion of the asynchronous importing task executing" +
" or the server is not responding. Try it later!"));
}
else
{
foreach (Task<IList<ResourceFreeBusyDto>> task in tasks)
{
result.AddRange(task.Result);
}
}
return result;
}
NOTE
var a = tasks.ToArray();
Task.WaitAll(a);
is the test where an exception is thrown. var a = tasks.ToArray(); is passing without an error.
Exception is thrown here:
Task.WaitAll(a);
and here
status = Task.WaitAll(tasks.ToArray(), timeout);
Can you please explain me what is happening? I can see during the debugging that tasks is initialized.
P.S. the commented lines for example: result.AddRange(GetResourceFreeBusyOnline(requesterId, meetingRoomIds.ToArray(), start, end)); are the lines calling the methods in single thread which is passing without an error and returning the expected result.
Stack
2016-12-22 13:24:18,844 [9] FATAL Tieto.MyMeetings.Tasks.FreeBusy.FreeBusyTasks - System.AggregateException: One or more errors occurred. ---> System.NullReferenceException: Object reference not set to an instance of an object.
at SharpArch.NHibernate.Web.Mvc.WebSessionStorage.GetSimpleSessionStorage()
at SharpArch.NHibernate.Web.Mvc.WebSessionStorage.GetSessionForKey(String factoryKey)
at SharpArch.NHibernate.NHibernateSession.CurrentFor(String factoryKey)
at SharpArch.NHibernate.NHibernateRepositoryWithTypedId`2.get_Session()
at SharpArch.NHibernate.LinqRepositoryWithTypedId`2.FindAll()
at Tieto.MyMeetings.Tasks.FreeBusy.FreeBusyTasks.GetResourceFreeBusyOnline(Int32 requesterId, Int32[] resourceIds, DateTime start, DateTime end) in C:\_Hg\Tieto.MyMeetings\source\server\Tieto.MyMeetings.Tasks\FreeBusy\FreeBusyTasks.cs:line 238
at Tieto.MyMeetings.Tasks.FreeBusy.FreeBusyTasks.<>c__DisplayClass7_0.<GetResourceFreeBusy>b__0() in C:\_Hg\Tieto.MyMeetings\source\server\Tieto.MyMeetings.Tasks\FreeBusy\FreeBusyTasks.cs:line 83
at System.Threading.Tasks.Task`1.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
--- End of inner exception stack trace ---
at System.Threading.Tasks.Task.WaitAll(Task[] tasks, Int32 millisecondsTimeout, CancellationToken cancellationToken)
at System.Threading.Tasks.Task.WaitAll(Task[] tasks, Int32 millisecondsTimeout)
at System.Threading.Tasks.Task.WaitAll(Task[] tasks)
at Tieto.MyMeetings.Tasks.FreeBusy.FreeBusyTasks.GetResourceFreeBusy(Int32 requesterId, Int32[] resourceIds, DateTime start, DateTime end) in C:\_Hg\Tieto.MyMeetings\source\server\Tieto.MyMeetings.Tasks\FreeBusy\FreeBusyTasks.cs:line 113
---> (Inner Exception #0) System.NullReferenceException: Object reference not set to an instance of an object.
at SharpArch.NHibernate.Web.Mvc.WebSessionStorage.GetSimpleSessionStorage()
at SharpArch.NHibernate.Web.Mvc.WebSessionStorage.GetSessionForKey(String factoryKey)
at SharpArch.NHibernate.NHibernateSession.CurrentFor(String factoryKey)
at SharpArch.NHibernate.NHibernateRepositoryWithTypedId`2.get_Session()
at SharpArch.NHibernate.LinqRepositoryWithTypedId`2.FindAll()
at Tieto.MyMeetings.Tasks.FreeBusy.FreeBusyTasks.GetResourceFreeBusyOnline(Int32 requesterId, Int32[] resourceIds, DateTime start, DateTime end) in C:\_Hg\Tieto.MyMeetings\source\server\Tieto.MyMeetings.Tasks\FreeBusy\FreeBusyTasks.cs:line 238
at Tieto.MyMeetings.Tasks.FreeBusy.FreeBusyTasks.<>c__DisplayClass7_0.<GetResourceFreeBusy>b__0() in C:\_Hg\Tieto.MyMeetings\source\server\Tieto.MyMeetings.Tasks\FreeBusy\FreeBusyTasks.cs:line 83
at System.Threading.Tasks.Task`1.InnerInvoke()
at System.Threading.Tasks.Task.Execute()<---
---> (Inner Exception #1) System.NullReferenceException: Object reference not set to an instance of an object.
at SharpArch.NHibernate.Web.Mvc.WebSessionStorage.GetSimpleSessionStorage()
at SharpArch.NHibernate.Web.Mvc.WebSessionStorage.GetSessionForKey(String factoryKey)
at SharpArch.NHibernate.NHibernateSession.CurrentFor(String factoryKey)
at SharpArch.NHibernate.NHibernateRepositoryWithTypedId`2.get_Session()
at SharpArch.NHibernate.LinqRepositoryWithTypedId`2.FindAll(ILinqSpecification`1 specification)
at Tieto.MyMeetings.Tasks.FreeBusy.FreeBusyTasks.GetMeetingRoomsFreeBusyCached(Int32 requesterId, Int32[] meetingRoomIds, DateTime start, DateTime end) in C:\_Hg\Tieto.MyMeetings\source\server\Tieto.MyMeetings.Tasks\FreeBusy\FreeBusyTasks.cs:line 196
at Tieto.MyMeetings.Tasks.FreeBusy.FreeBusyTasks.<>c__DisplayClass7_0.<GetResourceFreeBusy>b__2() in C:\_Hg\Tieto.MyMeetings\source\server\Tieto.MyMeetings.Tasks\FreeBusy\FreeBusyTasks.cs:line 103
at System.Threading.Tasks.Task`1.InnerInvoke()
at System.Threading.Tasks.Task.Execute()<---
Task.WaitAll doesn't throw this exception. It will rethrow exceptions raised by one of its tasks. Without the full exception and call stack (as returned by Exception.ToString()) it's impossible to be sure, but the standard guidance applies here as well - somewhere, somehow, an unininitialized variable or parameter is used.
For example:
List<int> personIds;
List<int> meetingRoomIds;
SplitResourceIds(resourceIds, out personIds, out meetingRoomIds);
may well set both personIds and meetingRoomIds to null. This means that
var task = Task.Factory.StartNew(() => GetResourceFreeBusyOnline(requesterId,
personIds.ToArray(),
start, end));
will throw an exception inside the task, that will be re-raised when Task.WaitAll is called.
To fix this just follow the advice in the duplicate question. Check parameters for null, debug your code, check the full call stack of the exception. Task.WaitAll will throw an AggregateException which contains all underlying exceptions in its InnerExceptions property.
At the very least you should catch the AggregateException to handle and log exceptions raised in tasks separately.
Finally, use async/await, Task.Run and await Task.WhenAll instead of StartNew and Task.WaitAll. await unwraps the AggregateException and raises the underlying exception, which makes debugging a lot easier.
UPDATE
From the call stack it appears that Tieto.MyMeetings.Tasks.FreeBusy.FreeBusyTasks.GetMeetingRoomsFreeBusyCached calls SharpArch.NHibernate.LinqRepositoryWithTypedId'2.FindAll and either passes a null parameter or a list of items that contain null. These values are probably used to create a session key, since SharpArch.NHibernate.Web.Mvc.WebSessionStorage.GetSessionForKey is called before the exception is thrown.
Finding the exact problem requires debugging the code and stepping into GetMeetingRoomsFreeBusyCached

Null reference - Task ContinueWith()

For the following piece of code (.NET v4.0.30319) I am getting a null reference exception indicated below in the second continuation.
Most interestingly this issue has only occurred in machines with 8GB RAM but other users have 16GB and more and they haven't reported any issue, and it is a very intermittent issue which leads me to suspect a garbage collection issue.
The GetData() can be called multiple times so the first continuation of _businessObjectTask will only be called once as _businessObjects will have been populated from that point forward.
I imagine the Object reference not set to an instance of an object exception is being thrown because either
_businessObjectTask is null and it can't continue from a null task.
items variable which is passed in as a parameter is null somehow
The line number in my log file (748) points to the one highlighted below and not the lambda expression which points to #1 above instead of #2. I've played around in Visual Studio and each of the lines following businessObjectTask.ContinueWith() are considered different i.e. if it was a null reference within the lambda expression it would give a different line number to 748
Any help would be greatly appreciated.
Edit:
This isn't related to What is a NullReferenceException, and how do I fix it? because that is a much more basic explanation of null reference whereas this is much more complicated and subtle.
Exception
Full details of the stack trace (edited for simplicity with dummy class and namespace names)
Object reference not set to an instance of an object.
at ApplicationNamespace.ClassName`1.<>c__DisplayClass4e.<GetData>b__44(Task`1 t) in e:\ClassName.cs:line 748
at System.Threading.Tasks.ContinuationTaskFromResultTask`1.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
Code
private static IDictionary<string, IBusinessObject> _businessObjects;
private Task<IDictionary<string, IBusinessObject>> _businessObjectTask;
public Task GetData(IList<TBusinessItem> items))
{
Log.Info("Starting GetData()");
if (_businessObjects == null)
{
var businessObjectService = ServiceLocator.Current.GetInstance<IBusinessObjectService>();
_businessObjectTask = businessObjectService.GetData(CancellationToken.None)
.ContinueWith
(
t =>
{
_businessObjects = t.Result.ToDictionary(e => e.ItemId);
return _businessObjects;
},
CancellationToken.None,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Current
);
}
var taskSetLEData = _businessObjectTask.ContinueWith // Line 748 in my code - "Object reference not set to an instance of an object." thrown here
(
task =>
{
items.ToList().ForEach
(
item =>
{
IBusinessObject businessObject;
_businessObjects.TryGetValue(item.Id, out businessObject);
item.BusinessObject = businessObject;
}
);
},
CancellationToken.None,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Default
);
}
Resolution:
So having used what I've learned from this question I went back to the original code and figured it all out.
Turns out the reason for this NRE was because the _businessObjectTask is non-static where as the _businessObjects is static.
This means that _businessObjects is null the first time GetData() is called which then sets _businessObjectTask to non-null. Then when _businessObjectTask.ContinueWith gets called it is non-null and continues without problem.
However if a second instance of this class above is instantiated, _businessObjects has already been populated so _businessObjectTask remains null. Then when _businessObjectTask.ContinueWith gets called, a NRE on _businessObjectTask is thrown.
There were a couple of options I could have taken but I ended up removing the _businessObjectTask to a synchronous method call which means that I dont need to use the continuation anymore and I set _businessObjects once.
This is a synchronization problem.
You are assuming that _businessObjectTask is always assigned before _businessObjects.
That is, however, not guaranteed. It is possible that your continuation that assign _businessObjects is executed inline and therefore before businessObjectService.GetData(...).ContinueWith(...) returns.
// This assignment could happend AFTER the inner assignment.
_businessObjectTask = businessObjectService.GetData(CancellationToken.None)
.ContinueWith
(
t =>
{
// This assignment could happen BEFORE the outer assignment.
_businessObjects = t.Result.ToDictionary(e => e.ItemId);
Therefore, it is possible that _businessObjects is not null although _businessObjectTask is null.
If a concurrent thread would enter your GetData method at that time it would clearly not enter
if (_businessObjects == null) // not entered because it's not null
{
...
}
...and instead continue with
var taskSetLEData = _businessObjectTask.ContinueWith // line 748
...which will cause a null reference exception since _businessObjectTask is null.
Here's how you could simplify your code and resolve this synchronization problem:
private Lazy<Task<IDictionary<string, IBusinessObject>>> _lazyTask =
new Lazy<Task<IDictionary<string, IBusinessObject>>>(FetchBusinessObjects);
private static async Task<IDictionary<string, IBusinessObject>> FetchBusinessObjects()
{
var businessObjectService = ServiceLocator.Current.GetInstance<IBusinessObjectService>();
return await businessObjectService.GetData(CancellationToken.None).ToDictionary(e => e.ItemId);
}
public async Task GetData(IList<TBusinessItem> items)
{
Log.Info("Starting GetData()");
var businessObjects = await _lazyTask.Value;
items.ToList().ForEach
(
item =>
{
IBusinessObject businessObject;
businessObjects.TryGetValue(item.Id, out businessObject);
item.BusinessObject = businessObject;
}
);
}
Notes:
Using Lazy<T> to ensure that the business object service is only invoked once (per instance of this class, whatever it is).
Using async/await to simplify code.
You may want to consider declaring _lazyTask as static. In your code there seem to be a mixup between static/non-static fields. I cannot know which is right for you.

Null Pointer exception in C#

I am facing NullPointerException in below code as it is happening very rarely and I tried to debug to replicate the issue but no luck. Can anybody help me what can cause NullPointerException here.
private static void MyTaskCompletedCallback(IAsyncResult res)
{
var worker = (AsyncErrorDelegate)((AsyncResult)res).AsyncDelegate;
var async = (AsyncOperation)res.AsyncState;
worker.EndInvoke(res);
lock (IsAsyncOpOccuring)
{
IsBusy = false;
}
var completedArgs = new AsyncCompletedEventArgs(null, false, null);
async.PostOperationCompleted(e => OnTaskCompleted((AsyncCompletedEventArgs)e), completedArgs);
}
Null Pointer exception is reported at
var async = (AsyncOperation)res.AsyncState;
Code from where I am invoking it
var context = HttpContext.Current;
AsyncErrorDelegate bkWorker = SendErrorMail;
AsyncCallback completedCallback = MyTaskCompletedCallback;
lock (IsAsyncOpOccuring)
{
if (IsBusy)
{
//Do we need to do something if repeated async getting call in case of error occuring at same time by different users.
}
AsyncOperation async = AsyncOperationManager.CreateOperation(null);
bkWorker.BeginInvoke(context,completedCallback, async);
IsBusy = true;
}
Null Pointer exception is reported at var async = (AsyncOperation)res.AsyncState;
We can logically deduce that this is not actually the case.
If the line before worked, we know that res is not null. AsyncState is object, so no custom operators are involved here, which means the cast is thus a type-check - which can either return null (without erroring), or can raise an invalid-cast exception.
If you are seeing a NullReferenceException, that leaves 2 options:
res is null and it is the line above that is erroring (which: we shouldn't actually expect - that will not happen)
the error is actually coming from EndInvoke, the line after
(the exact line often gets slightly confused when an exception is involved).
I suggest you add logging between each, to track what is happening. I also suggest you explicitly try around the EndInvoke, since that can throw exceptions (it re-throws any exception from the async operation).
In the more general case, a third option would have been:
AsyncOperation is a struct, and AsyncState is null
However, in this case we can rule that out by deduction, because if AsyncOperation were a struct, the following would never box to null (only an empty AsyncOperation? would box to null):
AsyncOperation async = AsyncOperationManager.CreateOperation(null);
bkWorker.BeginInvoke(context,completedCallback, async);
Should
var async = (AsyncOperation)asyncResult.AsyncState;
not be
var async = (AsyncOperation)res.AsyncState;
?

Categories

Resources