C# 如何捕获异步HttpClient调用中的所有异常?

C# 如何捕获异步HttpClient调用中的所有异常?,c#,.net,.net-4.0,httpclient,C#,.net,.net 4.0,Httpclient,我有以下下载网页的功能: static bool myFunction(int nmsTimeout, out string strOutErrDesc) { //'nmsTimeout' = timeout in ms for connection //'strOutErrDesc' = receives error description as string bool bRes = false; strOutEr

我有以下下载网页的功能:

    static bool myFunction(int nmsTimeout, out string strOutErrDesc)
    {
        //'nmsTimeout' = timeout in ms for connection
        //'strOutErrDesc' = receives error description as string
        bool bRes = false;
        strOutErrDesc = "";

        HttpClient httpClient = null;
        System.Threading.Tasks.Task<string> tsk = null;

        try
        {
            httpClient = new HttpClient();
            tsk = httpClient.GetStringAsync("https://website-to-connet.com");

            if (tsk.Wait(nmsTimeout))
            {
                if (tsk.Status == System.Threading.Tasks.TaskStatus.RanToCompletion)
                {
                    string strRes = tsk.Result;
                    strRes = strRes.Trim();

                    if (!string.IsNullOrWhiteSpace(strRes))
                    {
                        bRes = true;
                    }
                    else
                    {
                        //Empty result
                        strOutErrDesc = "Empty result";
                    }
                }
                else
                {
                    //Bad task completion
                    strOutErrDesc = "Bad completion result: " + tsk.Status.ToString();
                }
            }
            else
            {
                //Timed out
                strOutErrDesc = "Timeout expired: " + nmsTimeout + " ms.";
            }
        }
        catch (Exception ex)
        {
            //Error
            strOutErrDesc = "Exception: " + ex.Message;
            if (tsk != null)
            {
                strOutErrDesc += " -- ";
                int c = 1;
                foreach(var exc in tsk.Exception.InnerExceptions)
                {
                    strOutErrDesc += c.ToString() + ". " + exc.InnerException.Message;
                }
            }

            bRes = false;
        }

        return bRes;
    }
static bool myFunction(int-nmsTimeout,out-string strouterderdesc)
{
//“nmsTimeout”=连接超时(毫秒)
//“StrouterDesc”=接收作为字符串的错误描述
布尔-布雷斯=假;
strooterderesc=“”;
HttpClient HttpClient=null;
System.Threading.Tasks.Task tsk=null;
尝试
{
httpClient=新的httpClient();
tsk=httpClient.GetStringAsync(“https://website-to-connet.com");
如果(tsk.等待(NMTIMEOUT))
{
if(tsk.Status==System.Threading.Tasks.TaskStatus.RanToCompletion)
{
字符串strRes=tsk.Result;
strRes=strRes.Trim();
如果(!string.IsNullOrWhiteSpace(strRes))
{
bRes=真;
}
其他的
{
//空结果
strouterderesc=“空结果”;
}
}
其他的
{
//任务完成不良
strooterderesc=“错误的完成结果:”+tsk.Status.ToString();
}
}
其他的
{
//超时
strouterDesc=“超时已过期:”+nmsTimeout+“毫秒”;
}
}
捕获(例外情况除外)
{
//错误
strooterderesc=“异常:”+ex.消息;
如果(tsk!=null)
{
StrouterDesc+=“--”;
int c=1;
foreach(tsk.Exception.InnerExceptions中的var exc)
{
strouterDesc+=c.ToString()++“+exc.InnerException.Message;
}
}
bRes=假;
}
返回布雷斯;
}
我认为我的
try
/
catch
构造足以捕获其中的所有异常

直到我发现以下异常和应用程序崩溃的Windows错误消息:

未处理的异常:System.AggregateException:任务的异常 未通过等待任务或访问其 异常属性。因此,未被发现的异常被重新发现 由终结器线程执行。-->System.Net.Http.HttpRequestException: 响应状态代码不表示成功:503(服务 不可用)

---内部异常堆栈跟踪结束--

在System.Threading.Tasks.TaskExceptionHolder.Finalize()中


这是什么?我如何捕获它?

应用程序崩溃了,因为try/catch不是“到处捕获”黑客,它只捕获在同一调用堆栈或同一调用上下文中抛出的异常

另一方面,由于某种原因,您使用同步方法来启动异步任务,这些任务在其他线程上运行,并且它们的上下文丢失


使用这些方法的同步版本,或者更好地在异步任务上使用async方法和
wait
,这将保留调用上下文,并允许您使用try/catch块捕获从内部引发的任何异常。

请注意,此处描述的是一种旧行为(如注释中所述)在.NET4.5及更新版本中也不会出现这种情况

发生的情况是任务没有成功完成,并且您没有检查错误。当垃圾收集器尝试清理
任务
对象时,它会在那里找到未处理的异常,并将其抛出到
聚合异常
中。也就是说,异常实际上并没有抛出到
try
块中(它甚至在不同的线程上),因此catch无法捕获它

您要做的是正确地等待创建的任务。此时,您可能需要阅读C#中的async/await。如果希望任务可取消,则可能必须使用带有取消令牌的
GetAsync
,或者必须等待
GetStringAsync
在某个点完成

如果出于某种原因不想使用异步等待方式(应该!),那么仍然可以使用
tsk.Wait()。但是,这将把抛出的异常包装在一个
聚合异常中
,调用将是同步的

如果你真的不能等待你的函数完成,你可以在中看到,如何通过继续任务自动处理异常检查


但是,我建议您使用async/await并正确地检查任务是如何完成的以及它们抛出了什么。

让任务类的终结器抛出此异常并不是很多用户以前见过的情况。这是.NET4.0的行为,当时他们仍然认为程序员意识到他们忘记检查异常是很重要的。在4.5中进行了更改,因为它很难诊断和处理。照目前的情况,等待超时足以点燃保险丝,如果它确实超时,则炸弹会在下一次垃圾收集时爆炸,这是事故发生后的一段随机时间。我认为您需要明确取消任务来修复它。但是请转到4.5++@HansPassant,这正是我选择的语言是母语(C或C++)的原因,我想控制所有这些事情。至于这段代码,这只是一个奇怪的球项目,我用自己作为一个“实用”应用程序。我在.net中写这篇文章主要是为了提高速度。我只是不喜欢它那样崩溃。我将尝试搜索
await
关键字以查看它是否修复了它。哎呀,刚刚意识到我不能使用
await
。。。。但是将它移动到
4.5
只是为了修复这个“过度思考”的GC异常有点愚蠢。你知道我如何在.NET4.0中抓住它吗?令人恼火的是,我不能用英语表达