Search code examples
c#asp.net.netdotnet-httpclient

C# HttpClient.SendAsync await throws NullReferenceException


First of all, I'm attempting to build a plugin for a product - so the environment is not mine and I don't have a ton of visibility into what's going on. As far as I can tell, the system is running .NET Framework 4.6.2, and I've tried to ensure all my dependencies match what's on the system.

The issue I'm seeing is that when I issue a await PostAsync(...) request, I get a NullReferenceException. This is 100% reproducible. However, if I make the exact same request using PostAsync(...).Result, everything works properly.

This is made stranger given that I can use the await keyword on the parsing of the response, and it still works correctly.

I want to do it the 'right' way, using the await keyword, but it just doesn't work. From everything I've been reading, it's likely a dependency issue, but I don't know where to look to find it. I've spent 3 days on this already, when I finally discovered that using .Result 'fixes' my issue.

Relevant code:

private async Task<T> aFunction<T>(string path, Dictionary<string, string> data) {
  var content = new FormUrlEncodedContent(data);
  var response = client.PostAsync(path, content).Result; // This works
  // var response = await client.PostAsync(path, content); // This does not
  response.EnsureSuccessStatusCode();
  var result = await response.Content.ReadAsStringAsync();
  var parsed = JsonConvert.DeserializeObject<T>(result);
  return parsed;
}

Stack Trace of the NRE:

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.Threading.Tasks.AwaitTaskContinuation.RunCallback(ContextCallback callback, Object state, Task& currentTask)
--- End of stack trace from previous location where exception was thrown ---
at System.Threading.Tasks.AwaitTaskContinuation.<>c.<ThrowAsyncIfNecessary>b__18_0(Object s)
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()

Solution

  • Since the ASPX page that was hosting the plugin is not async, I could not use async methods inside my handler. I ended up having to ensure that I use synchronous calls instead of the async ones.

    To prove this, after adding the line (recommended by Martin)

    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
    

    and using the await calls, my plugin started to hang after making the request.

    This SO page: Async Deadlock?, implied to me that the page on which my plugin was sitting was not properly setup for async functionality. I tried adding the RegisterAsyncTask, and in the logs I see a new message:

    InvalidOperationException: This operation requires the page to be asynchronous (the Async attribute must be set to true).