【问题标题】:Failed to send any data, Sometimes TaskCanceledException catched, when httpClient.GetAsync() is called [duplicate]调用 httpClient.GetAsync() 时,发送任何数据失败,有时捕获到 TaskCanceledException [重复]
【发布时间】:2020-06-26 08:04:07
【问题描述】:

我开发了 C# .net 4.6.1 应用程序(这是Windows Service Application)。在应用程序中,我使用 HttpClient 与我们的后端 API 进行通信。随着时间的推移,应用程序不会向我们的后端发送请求(TaskCanceledException 被捕获)。

这里是这个异常的堆栈跟踪

System.Threading.Tasks.TaskCanceledException · 任务被取消。

System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
TestHttpClient+<GetRequestAsync>d__20.MoveNext()

这个问题有两个原因解决

  1. 重启应用后
  2. 重复尝试相同的请求时

我使用Fiddler 调查了这个问题,当异常发生且处于正常状态时

  1. In normal mode fiddler shows 2 requests
  • Result=200,Protocol=HTTP,Host = Tunnel to,Url = api.example.com:443

在 SyntaxView 选项卡中:找到了与 SSLv3 兼容的 ClientHello 握手。 Fiddler 提取了以下参数。

  • 结果 = 200,协议 = HTTPS,主机 = api.example.com,网址 = /test
  1. In failed mode Fiddler shows only 1 requst
  • Result=200,Protocol=HTTP,Host = Tunnel to,Url = api.example.com:443,

在 SyntaxView 选项卡中: 客户收到通知后 建立CONNECT,发送数据失败

这是我们代码的 sn-p。

class Program
{
  static void Main(string[] args)
  {
    // I Tried also without  Task.Run() i.e. MyClass myClass = GetAsync().Result; 
    MyClass myClass = Task.Run(() => GetAsync().Result).Result ;
  }
        
  private static async Task<MyClass> GetAsync()
  {
    // I Tried also without .ConfigureAwait(false)
     MyClassResponse myClassResponse = await TestHttpClient.GetMyClassResponse().ConfigureAwait(false);
    return MyClass.Create(myClassResponse);
  }
}
public static class TestHttpClient
{
   private static HttpClient _httpClient;
   
   public static void Init()
   {
      ServicePointManager.SecurityProtocol |= (SecurityProtocolType.Ssl3 | 
                SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | 
                                             SecurityProtocolType.Tls);
      ServicePointManager.DefaultConnectionLimit = 10;
     _httpClient = CreateHttpClient();
   }

   private static HttpClient CreateHttpClient()
   {
      HttpClient client = new HttpClient();
      client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("token .....");
      client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
      client.Timeout = TimeSpan.FromMilliseconds(10000);
      return client;
    }

   public static async Task<MyClassResponse> GetMyClassResponse()
   {
      HttpResponseMessage response = await GetRequestAsync("https://api.example.com/test");
      return await ParseToMyClassResponse<MyClassResponse>(response);
   }
 
    private static async Task<HttpResponseMessage> GetRequestAsync(string url)
   {
        try
        {
           return await _httpClient.GetAsync(new Uri(url));
         }
         catch (TaskCanceledException)
         {
          return new HttpResponseMessage(HttpStatusCode.RequestTimeout);
         }
    }

    private static async Task<T> ParseToMyClassResponse<T>(HttpResponseMessage response) where T : MyClassResponse, new()
        {
            T myClassResponse;
            try
            {
                string content = await response.Content.ReadAsStringAsync();
                response.EnsureSuccessStatusCode();
                myClassResponse = JsonConvert.DeserializeObject<T>(content);
                           }
            catch (Exception ex)
            {
                myClassResponse = new T();
            }

            response.Dispose();
            return myClassResponse;
        }
}

我做错了什么?

为什么Fiddler 显示"After the client received notice of the established CONNECT, it failed to send any data" 消息。

【问题讨论】:

  • 你不应该再使用SecurityProtocolType.Ssl3。 SSLv3 严重不安全且过时。大多数服务器甚至不再支持 SSLv3 客户端 hello。
  • 嗨@罗伯特。感谢您的快速回复。我们有一个System.Net.WebException: The request was aborted: Could not create SSL/TLS secure channel。添加SecurityProtocolType.Ssl3 后,异常消失了。如果异常与SecurityProtocolType.Ssl3有关,那么为什么有时会发生这种情况。
  • 服务器是 Internet 上的公共服务器还是您的 Intranet 中的私有服务器?如果您在控制服务器,则应检查它,因为如果它需要 SSLv3 握手,则非常不安全。我认为这也是 Fiddler 的问题。根据使用的 Windows 版本,Fiddler 也不使用 SSLv3 握手,因此无法建立与服务器的连接。
  • @Robert 服务器是公开的。在服务器上我们使用TLS。我不知道为什么,但没有SecurityProtocolType.Ssl3 我们有System.Net.WebException 异常。我想指出System.Net.WebException 异常并不总是发生。好的,我会尝试删除SecurityProtocolType.Ssl3,如果System.Net.WebException 异常会再次出现。我会开启一个新的讨论。你能解释一下为什么这个错误并不总是发生吗?
  • 如果他的服务器是公开的,我建议对其执行 SSL 测试:ssllabs.com/ssltest 另外请记住,对于 Dot.net 程序,使用的是 Windows 的 SSL/TLS 实现。因此,如果您使用过时的操作系统,如 Win7 或 XP,它可能会因为操作系统而失败。

标签: c# .net async-await fiddler dotnet-httpclient


【解决方案1】:
  1. 首先替换这个
static void Main(string[] args)
{
    // I Tried also without  Task.Run() i.e. MyClass myClass = GetAsync().Result; 
    MyClass myClass = Task.Run(() => GetAsync().Result).Result ;
}

与 whis

static async Task Main(string[] args)
{
    MyClass myClass = await GetAsync();
    
    Console.WriteLine("All done.");
    Console.ReadKey();
}
  1. HttpResponseMessageIDisposable,请妥善处理
public static async Task<MyClassResponse> GetMyClassResponse()
{
    using (HttpResponseMessage response = await GetRequestAsync("https://api.example.com/test"))
    {
        return await ParseToMyClassResponse<MyClassResponse>(response);
    }
}

并从ParseToMyClassResponse 中删除response.Dispose()

  1. 为了进行额外的测试,我重写了代码以使其更简单。
public class Program
{
    private readonly static HttpClient client = new HttpClient();

    static async Task Main(string[] args)
    {
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("token .....");
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        client.Timeout = TimeSpan.FromMilliseconds(10000);
        ServicePointManager.DefaultConnectionLimit = 10;

        try
        {
            MyClassResponse myClassResponse = await GetAPIResponseAsync("https://api.example.com/test");
            MyClass myClass = MyClass.Create(myClassResponse);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            Console.WriteLine();
            Console.WriteLine(ex.StackTrace);
        }

        Console.WriteLine("All done.");
        Console.ReadKey();
    }

    private static async Task<T> GetAPIResponseAsync<T>(string url)
    {
        using (HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
        {
            response.EnsureSuccessStatusCode();
            string content = await response.Content.ReadAsStringAsync();
            return JsonConvert.DeserializeObject<T>(content);
        }
    }
}

测试这个版本。

此外,不建议在代码中更改SecurityProtocol,因为这会使应用程序易受攻击,请改用默认值。如果您想更改 TLS 版本进行测试,edit Windows Registry


更新

我看过源代码。

这是一种糟糕的并发编程。您在任务中运行异步函数并阻塞正在执行它的任务并同时阻塞当前线程。

Task<PassResponse> task = Task.Run(() => GetPass(scannedValue).Result);
PassResponse passResponse = task.Result;

这样你可能会得到同样的结果

PassResponse passResponse = GetPass(scannedValue).Result;

再试一次

让我们改变这一行以避免可能的死锁

return await _httpClient.GetAsync(new Uri(url));

return await _httpClient.GetAsync(new Uri(url)).ConfigureAwait(false);

还有这个

string content = await response.Content.ReadAsStringAsync();

string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

【讨论】:

  • 嗨@aepot。非常感谢您重写代码,但我只显示了我项目中的 sn-p,因此我无法使用您的代码。至于第一条评论:我需要将代码锁定在这个地方,所以我使用了.Result(但我会考虑如何避免代码阻塞)。我改变了SecurityProtocol,因为我抓住了System.Net.WebException: The request was aborted: Could not create SSL/TLS secure channel。你知道为什么会抛出这个异常吗?
  • @Aram 如果您使用.Result.GetAwaiter().GetResult(),则表示出现问题。它称为sync-over-async 方法,这是获得死锁的最简单方法。避免锁定主线程。对于 SSL,至少从列表中删除不安全的 SSLv3。
  • @Aram 注意ResponseHeadersReadEnsureSuccessStatusCode() 在加载数据之前移动了。
  • @Aram 你能用.Result展示真正的方法吗?我确定它可以重写为async one。
  • 嗨@aepot。我很感激你的帮助。这个项目太大了,所以我把它删掉了,只留下重要的代码。我们与其他服务集成。服务通过 Http 请求与我们的插件通信。出于某种原因,我们必须在本地计算机上进行。在此之前,我们使用 Nancy 框架来提供通信。
猜你喜欢
  • 2020-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多