17

我的 MVC 4.0 应用程序中有一个奇怪的问题。我使用 REST Web 服务(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 没有任何问题。做了一个小单元测试,使用相同的特定 URL 就可以了...

当它在调试器中爆炸时,很难看出有什么问题。(没有抛出异常!)。最后,我通过 IntelliTrace 看到有异常,似乎在System.Threading.Tasks. 很难确定,因为调用堆栈对于我的非专家眼来说有点令人困惑......

这是我从之前的代码中获得的调用堆栈:

>   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#

无论如何,这看起来肯定与任务、异步、后台工作人员等有关......有没有一种好方法可以“清除”所有其他正在运行的任务,以避免这个问题?

谢谢你的帮助,伯纳德。

4

3 回答 3

33

添加到@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 试图重新加入一个已经被清空的上下文,所以它在 try/catch 之外抛出了 NullReferenceException。

这很难诊断。在生产环境中,您不会在 try/catch 中看到任何内容,而在 Visual Studio 中,计划重新加入原始上下文的 await 有点随机 - 这取决于 TaskScheduler 碰巧决定做什么。

如果您不想触发并忘记了明显的答案是等待异步方法 - 您将收到一个编译器警告,提醒您这样做。

如果您确实想解雇并忘记解决方案是显式启动一个新任务。这个关于fire and forget Tasks的答案涵盖了最好的方法。

于 2013-12-04T02:45:16.143 回答
7

从外观上看,您的代码可以在第一个 try 块的异常事件中休眠的线程之一被解决之前完成,因此在它们醒来 5 分钟后,没有原始线程可以重新加入,导致来自 AssociateWithCurrentThread 的 NullReferenceException

于 2013-05-17T19:01:45.353 回答
0

当我得到上面显示的异常和调用堆栈时,这是因为我试图使用异步执行“一劳永逸”,这是一个非常糟糕的主意。我切换到为我想要的新线程旋转,并且崩溃消失了。

于 2014-04-08T22:38:39.230 回答