调用Task.Result时出现ThreadAbortException

本文关键字:ThreadAbortException Task Result 调用 | 更新日期: 2023-09-27 18:21:42

我有以下代码,我试图使用HttpClient:向远程端点发出请求

using (var client = new HttpClient())
{
   client.BaseAddress = _serviceBaseAddress;
   Task<HttpResponseMessage> readResponseTask = client.GetAsync(relativeUri);
   readResponseTask.Wait();
   using (var response = readResponseTask.Result)
   {
     if (response.StatusCode == HttpStatusCode.NotFound || !response.IsSuccessStatusCode)
     {
       return default(TResult);
     }
     Task<TResult> readContentTask = response.Content.ReadAsAsync<TResult>();
     readContentTask.Wait();
     TResult value = readContentTask.Result;
     return value;
   }
 }

有时我会在readResponseTask.Result上得到ThreadAbortException,就像这样:

System.Threading.ThreadAbortException:线程被中止。在System.Threading.Monitor.OjWait(布尔exitContext,Int32毫秒超时,对象obj)System.Threading.ManualResetEventSlim.Wait(Int32毫秒超时,CancellationToken取消令牌)System.Threading.Tasks.Task.SpinThenBlockingWait(Int32毫秒超时,CancellationToken取消令牌)System.Threading.Tasks.Task.InteralWait(Int32毫秒超时,CancellationToken取消令牌)System.Threading.Tasks.Task.Wait(Int32毫秒超时,CancellationToken CancellationToken)

在什么情况下.Result会抛出这样的异常?我曾尝试在远程端点上模拟超时,但在.Wait()而不是.Result处出现异常。由于异常发生在.Wait()之后,我假设已经从远程站点返回了结果,但在尝试访问结果时不知怎么出了问题。

有线索吗?这可能与线程并发有关吗?

调用Task.Result时出现ThreadAbortException

我会在readResponseTask中得到ThreadAbortException。Result

不,你没有。调用堆栈清楚地表明,实际上是Wait()调用产生了异常。请注意"等待"一词在跟踪中的频繁出现。

很难看出你是怎么弄糊涂的。请记住,Task.Result属性getter非常小,并且将在运行程序的Release构建时内联。因此,您永远无法在堆栈跟踪中看到它。

也许您只需删除Wait()调用就可以了。没有必要,Result属性getter已经在必要时执行了等待。

Wait()期间,线程从外部中止。没有人能确定为什么。

启用网络客户端跟踪可以帮助检测根本原因。

首先,如果要立即调用.Wait(),请不要使用.*Async()。这是一种糟糕的做法,很可能会导致错误的结果。相反,使用调用的同步版本client.Get(relativeUri)和以下内容:

TResult value = response.Content.ReadAs<TResult>();    
return value;

如果您不打算利用.NET框架异步编程模型。

但是,如果您更愿意利用异步I/O功能,则应该遵循最佳实践。使用async/await关键字,使方法看起来像是同步的,同时利用.NET框架的异步关键字的功能。

我只能想象你的方法的入口点看起来是这样的:

public TResult InvokeClientGet<TResult>(string relativeUri) 
{  
    // ... left out for brevity
}

这实际上阻止了您使用async/await关键字。相反,请尝试以下操作:

    public async Task<TResult> InvokeClientGet<TResult>(string relativeUri)
    {
        try
        {
            using (var client = new HttpClient { BaseAddress = _serviceBaseAddress })
            {
                using (var response = await client.GetAsync(relativeUri))
                {
                    if (response.StatusCode == HttpStatusCode.NotFound || 
                        !response.IsSuccessStatusCode)
                    {
                        return default(TResult);
                    }
                    return await response.Content.ReadAsAsync<TResult>();
            }
        }
        catch (Exception ex)
        {
            // Handle exceptional conditions
        }
    }

关于System.Threading.ThreadAbortException.的几句话

当调用Abort方法来销毁线程时,公共语言运行库会抛出ThreadAbortException。ThreadAbortException是一个可以捕获的特殊异常,但它将在catch块结束时自动再次引发。当引发此异常时,运行时会在结束线程之前执行所有finally块。因为线程可以在finally块中进行无边界计算,或者调用thread.ResetArtrt来取消中止,所以无法保证线程会结束。如果要等待中止的线程结束,可以调用thread.Join方法。Join是一个阻塞调用,在线程实际停止执行之前不会返回。

话虽如此,如果有什么东西正在调用.Abort()并导致此异常。你没什么办法可以阻止它。无论如何,试着遵循最佳实践。

我在通过单元测试向WebService发送数据的方法时遇到了同样的问题。(ik有更好的方法可以做到这一点)

问题是单元测试调用了我的异步方法,但没有等到WebService调用完成。这中止了WebService调用,我得到了一个ThreadAbortException

[TestMethod]
public void WebServiceTest01(){
    BusinessLogic bs = new BusinessLogic();
    bs.CallWebService();
}

我所需要做的就是添加关键字await,让单元测试等待WebService调用完成。

[TestMethod]
public void WebServiceTest01(){
    BusinessLogic bs = new BusinessLogic();
    var result = await bs.CallWebService();
    Assert.NotNull(result);
}