调用Task.Result时出现ThreadAbortException异常

8

我有下面这段代码,我想使用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.ObjWait(Boolean exitContext, Int32 millisecondsTimeout, Object obj) 在 System.Threading.ManualResetEventSlim.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) 在 System.Threading.Tasks.Task.SpinThenBlockingWait(Int32 millisecondsTimeout, CancellationToken cancellationToken) 在 System.Threading.Tasks.Task.InternalWait(Int32 millisecondsTimeout, CancellationToken cancellationToken) 在 System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)

在什么情况下会导致.Result抛出这样的异常?我曾试图模拟远程终端上的超时,但却在.Wait()处而不是.Result处接到异常。 由于异常发生在.Wait()之后,我猜测结果已经从远程站点返回,但在尝试访问结果时发生了错误。

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


是否存在内部异常? - Yuval Itzchakov
不要输出内部异常。 - user1928346
如果您立即等待它们,为什么要使用“Async”? - VMAtm
你尝试启用网络客户端跟踪日志并查看异常再次发生时的日志了吗? - Oguz Ozgul
4个回答

7

我在 readResponseTask.Result 处遇到 ThreadAbortException 异常。

不,实际上是 Wait() 调用产生了异常。请注意跟踪信息中 "wait" 一词的频繁出现。

很难理解你为什么会混淆。请记住,Task.Result 属性 getter 方法 非常简短,当您运行 Release 版本的程序时,它将被内联。因此,您永远不能在堆栈跟踪中看到它。

也许您只需删除 Wait() 调用就可以提前完成。这不是必要的,Result 属性 getter 方法已经在必要时执行等待。


4

在Wait()期间,线程被外部中止。没有人能够确定原因。

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


3
首先,如果您要立即调用.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.ResetAbort 来取消中止,因此不能保证线程将永远结束。如果您想等待中止的线程结束,可以调用 Thread.Join 方法。Join 是一种阻塞调用,直到线程实际停止执行才返回。
话虽如此,如果某些东西正在调用 .Abort() 并导致此异常。你不能做太多事情来防止它。无论如何,请尝试遵循最佳实践。

0

我在通过单元测试发送数据到 WebService 的方法时遇到了同样的问题。(我知道有更好的方法来做这个)

问题是单元测试调用了我的异步方法,但没有等待 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);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接