C# Task - LazyAsyncResult - NullReferenceException

Sal Datoccio 96 Reputation points
2021-01-22T14:44:52.577+00:00

There is something wrong with the following method:

   public async Task<ExtendedProductDTO> GetProductByUniqueIDAsync(string id)  
            {  
                var product = await _productRepo.FindFirstOrDefaultAsync(i => i.UniqueID == id).ConfigureAwait(false);  
                return new ExtendedProductDTO(product != null, product);  
            **}**  

product is populated correctly and using a debugger I can reach the final } without any problems, but after that I got a weird NullReferenceException.
The exception happens after:
59560-pic-2.png

Notes:

  • ExtendedProductDTO just sets 2 fields (a boolean "Found" property and the actual product)
  • product is correctly retrieved by FindFirstOrDefaultAsync
  • In order to understand what's happening I am invoking the method as follows, but the execution halts after the first call. So product4 = ... and the catch clause are never reached
    59682-pic-3.png
  • I also have a sync method (very similar, just sync) and it works without any problems
  • There are also references to .NET Framework calls, see images below:
    59683-pic-1.png
    59684-pic-4.png

I really don't understand what's happening here...

C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,381 questions
.NET Runtime
.NET Runtime
.NET: Microsoft Technologies based on the .NET software framework.Runtime: An environment required to run apps that aren't compiled to machine language.
1,131 questions
{count} votes

Accepted answer
  1. Sal Datoccio 96 Reputation points
    2021-01-26T16:00:24.917+00:00

    I found the root cause of this issue.

    It was not in the:

    • MongoDB driver (async)
    • My repository (async)
    • Application layer (async)
    • ASP.NET controller (async)

    The async controller method was invoked synchronously by the caller and that caused the exception: invoking it in the proper way (=asynchronously) avoids the exception.

    Thank you for your help!

    0 comments No comments

10 additional answers

Sort by: Most helpful
  1. Sal Datoccio 96 Reputation points
    2021-01-26T15:48:36.343+00:00

    Thank you for your feedback,

    but adding <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" /> did not help.

    • If you hit the breakpoint after the async call in the controller's action but it blows up before returning from the method then that would seem to indicate an issue either in the runtime --> The exception is triggered before returning to the caller (Controller) and it's not handled by the try/catch block
    • This issue appears to be limited to calls to one specific method FirstOrDefaultAsync (MongoDB C# driver)
    • _collection.Find(predicate).FirstOrDefaultAsync().Result; works without triggering any exception. _collection?.Find(predicate).FirstOrDefaultAsync() ?? null; does not
    • Both methods return the expected value, then something weird happens in case of the async one

    I had a look at the implementation of FirstOrDefaultAsync:

    public static async Task<TDocument> FirstOrDefaultAsync<TDocument>(this IAsyncCursor<TDocument> cursor, CancellationToken cancellationToken = default(CancellationToken))
            {
                using (cursor)
                {
                    var batch = await GetFirstBatchAsync(cursor, cancellationToken).ConfigureAwait(false);
                    return batch.FirstOrDefault();
                }
            }
    

    where GetFirstBatchAsync is:

    private static async Task<IEnumerable<TDocument>> GetFirstBatchAsync<TDocument>(IAsyncCursor<TDocument> cursor, CancellationToken cancellationToken)
            {
                if (await cursor.MoveNextAsync(cancellationToken).ConfigureAwait(false))
                {
                    return cursor.Current;
                }
                else
                {
                    return Enumerable.Empty<TDocument>();
                }
            }
    

    So there shouldn't be anything in my code / MongoDB driver that could cause that null reference exception.
    The lack of a stack trace seems to tell me that the issue happens actually inside the runtime (or within the DI: Castle.Windsor 5.1.x, for some obscure reasons)

    0 comments No comments

  2. Michael Taylor 49,246 Reputation points
    2021-01-26T16:02:42.867+00:00

    So _collection is an IAsyncCursor in your original code example? And based upon the implementation of FirstOrDefaultAsync that you said it is using then it is disposable which means it'll get automatically cleaned up when the call returns. That doesn't seem right to me but it also wouldn't cause the exception you're talking about.

    It does seem like a lifetime issue to me. Looking at the native call stack should give you an idea of where. I'm going to say it is likely a bug in the MongoDB implementation as it is responsible for handling the LINQ queries and it appears to be having a lifetime issue problem. As I mentioned before I've seen this kind of thing when the DI container messes up the lifetime of objects and/or ORMs attempt to access the DB after it has been disposed. The native call stack might provide an indicator of where it is failing. The fact that the call is returning with a valid value before it fails seems to indicate maybe a problem cleaning up resources as the DB connection is dropped.