NullReferenceException в System.Threading.Tasks, вызывающий HttpClient.GetAsync(url)

У меня странная проблема в моем приложении MVC 4.0. Я использую веб-службы REST (Amazon Associate). Я создал метод, который я использую везде. Укороченная версия такова:

    private async Task<XElement> GetRequest(string url)
    {
        string myresponse;
        HttpResponseMessage response = null;
        HttpClient client = new HttpClient();            
        try
        {
            response = await client.GetAsync(url);
            myresponse = response.Content.ToString();
            if (myresponse.Contains("503"))
            {
                myTrace.WriteLine("503 Sleep.....");
                Thread.Sleep(3000); // looks like amazon us does not like fast requests....
                return await GetRequest(url); //restart after pausing....
            }
        }
        catch (TaskCanceledException ex)
        {
            myTrace.WriteLine("TaskCancelled From GetRequest: " + ex);
            return null;
        }

        catch (HttpRequestException ex)
        {
            myTrace.WriteLine("RequestException Sleep.....");
            Thread.Sleep(300000); // 5 minutes de pause 
        }

        catch (Exception ex)
        {
            myTrace.WriteLine("From GetRequest: " + ex);
            return null;
        }

        try
        {
            XElement content = await response.Content.ReadAsAsync<XElement>();
            response.Dispose();
            client.Dispose();
            return content;
        }
        catch (Exception)
        {
            return null;
        }
    }

Ничего особенного, он работает отлично. Но теперь, по конкретному звонку, он бомбит на client.GetAsync(url). Сначала я подозревал, что что-то в URL-адресе ошибочно, поэтому я схватил его из сеанса отладчика и вставил его прямо в свой браузер, получил ожидаемый ответ...

Итак, ничего плохого в URL-адресе. Сделано немного Unit Test, отлично работает с тем же конкретным URL-адресом...

Как он бомбит в отладчике, трудно понять, что случилось. (Исключений нет!). Наконец, я видел с IntelliTrace, что есть исключения, кажущиеся внутри System.Threading.Tasks. Трудно указать, как вызов Stack немного запутан для моих глазных экспертов...

Вот стек вызовов, который я получаю от предыдущего прохода в коде:

>   System.Web.dll!System.Web.ThreadContext.AssociateWithCurrentThread(bool setImpersonationContext = {unknown})    C#
System.Web.dll!System.Web.HttpApplication.OnThreadEnterPrivate(bool setImpersonationContext = {unknown})    C#
System.Web.dll!System.Web.HttpApplication.OnThreadEnter()   C#
System.Web.dll!System.Web.HttpApplication.System.Web.Util.ISyncContext.Enter()  C#
System.Web.dll!System.Web.Util.SynchronizationHelper.SafeWrapCallback(System.Action action = {unknown}) C#
System.Web.dll!<>c__DisplayClass9.AnonymousMethod(System.Threading.Tasks.Task _ = {unknown})    C#
mscorlib.dll!System.Threading.Tasks.ContinuationTaskFromTask.InnerInvoke()  C#
mscorlib.dll!System.Threading.Tasks.Task.Execute()  C#
mscorlib.dll!System.Threading.Tasks.Task.ExecutionContextCallback(object obj = {unknown})   C#
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext = {unknown}, System.Threading.ContextCallback callback = {unknown}, object state = {unknown}, bool preserveSyncCtx = {unknown})   C#
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext = {unknown}, System.Threading.ContextCallback callback = {unknown}, object state = {unknown}, bool preserveSyncCtx = {unknown})   C#
mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot = {unknown})    C#
mscorlib.dll!System.Threading.Tasks.Task.ExecuteEntry(bool bPreventDoubleExecution = {unknown}) C#
mscorlib.dll!System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() C#
mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()    C#
mscorlib.dll!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() C#

В любом случае это выглядит определенно связанным с задачами, Async, фоновыми работниками и т.д. Есть ли хороший способ "очистить" все остальные запущенные задачи, чтобы избежать этой проблемы?

Спасибо за вашу помощь, Бернард.

Ответ 1

Добавляя к @kcar ответ, у меня была очень похожая проблема, когда было несколько ожиданий на пути к коду, у которого был единственный метод, который не ожидал, например:

public async Task JsonResult BookThing(InputModel model)
{
    // Do some stuff
    thisIsAnAsyncMethod(Model model); // Fire and forget
    return Json(null);
}

protected async Task thisIsAnAsyncMethod(Model model)
{
    await oneThing();
    await anotherThing();
    await somethingElse();
}

Это вызвало ожидание случайного сбоя, не позволяя мне поймать исключение - потому что TPL пыталась воссоединиться с контекстом, который был заглушен, поэтому он выбрасывал исключение NullReferenceException вне try/catch.

Это очень трудно диагностировать. В Production вы ничего не увидите в try/catch, а в Visual Studio, который ожидает, что запланировано вернуться к исходному контексту, будет несколько случайным - это зависит от того, что решает TaskScheduler.

Если вы не хотите запускать и забывать очевидный ответ - ждать метода async - у вас будет предупреждение о компиляторе, напоминающее вам об этом.

Если вы хотите запустить и забыть решение, нужно явно запустить новую задачу. Лучший способ сделать это в этом ответе о пожаре и забыть Задачи.

Ответ 2

По внешнему виду ваш код может завершиться до того, как один из потоков, посланных спать в событии исключения из первого блока try, будет разрешен, поэтому через 5 минут, когда они просыпаются, нет исходного потока для rejoin, в результате чего NullReferenceException из AssociateWithCurrentThread

Ответ 3

Когда я получал стек исключений и вызовов, показанный выше, это было потому, что я пытался выполнить "огонь и забыть" выполнение с помощью async, что было очень плохой идеей. Я переключился на прядение новой нити за то, что я хотел, и аварии исчезли.