-->

Async logging throwing a NullReferenceException

2019-02-09 11:12发布

问题:

I am trying to asynchronously log some information to SQL Server inside of an MVC 4 controller action targeting .NET 4.0 using the AsyncTargetingPack. I would jump straight to .NET 4.5 but my app lives in Azure and we're still waiting for the update...

This code works as expected (a row is written to my database with no exceptions thrown):

public class SystemActionLogger : ISystemActionLogger
{
    private readonly ActionBlock<Tuple<SystemAction, object>> actionBlock;

    public SystemActionLogger(ISystemActionLogEntryRepository repository)
    {
        actionBlock = new ActionBlock<Tuple<SystemAction, object>>(
            entry => TaskEx.Run(async () =>
                {
                    string data = await JsonConvert.SerializeObjectAsync(entry.Item2);
                    await repository.PersistAsync(new SystemActionLogEntry(entry.Item1, data));
                }));
    }

    public void Log(SystemAction systemAction, object data)
    {
        actionBlock.Post(new Tuple<SystemAction, object>(systemAction, data));
    }
}

And this code throws a NullReferenceException:

public class SystemActionLogger : ISystemActionLogger
{
    private readonly ActionBlock<Tuple<SystemAction, object>> actionBlock;

    public SystemActionLogger(ISystemActionLogEntryRepository repository)
    {
        actionBlock = new ActionBlock<Tuple<SystemAction, object>>(async entry =>
            {
                string data = await JsonConvert.SerializeObjectAsync(entry.Item2);
                await repository.PersistAsync(new SystemActionLogEntry(entry.Item1, data));
            });
    }

    public void Log(SystemAction systemAction, object data)
    {
        actionBlock.Post(new Tuple<SystemAction, object>(systemAction, data));
    }
}

NullReferenceException: "Object reference not set to an instance of an object."

Server stack trace: 
   at System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext)
   at System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext)
   at System.Web.LegacyAspNetSynchronizationContext.CallCallbackPossiblyUnderLock(SendOrPostCallback callback, Object state)
   at System.Web.LegacyAspNetSynchronizationContext.CallCallback(SendOrPostCallback callback, Object state)
   at System.Runtime.CompilerServices.TaskAwaiter.<>c__DisplayClassa.<OnCompletedInternal>b__0(Task param0)

Exception rethrown at [0]: 
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.<ThrowAsync>b__1(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()

I have no visibility into the exception as it is all external code. I don't understand why the second block of code fails. This is the code I originally wrote.

What am I doing wrong?

回答1:

I was just having a very similar issue (NullReference from AssociateWithCurrentThread when using a logging task).

The issue I was having was that the original action did not await the completion of the logging task, so when the log finishes and tries to rejoin the original thread a nullReference is thrown because the original thread has terminated.

This was solved for me by making sure that the request controller awaited the logging function.



回答2:

Dataflow only works on .NET 4.5. The fact that you're running it on .NET 4.0 is unsupported and is likely why you're seeing spurious exceptions.



回答3:

I had this problem with .net4.5 when my web service was invoking another WCF service in an async manner. I simply appended a short timed Wait() as I didn't care about the response (telemetry event).

public static void Event(string key, string message) {
    Telemetry.Event(key, message).Wait(100);
}