【问题标题】:Exception in a timer with an aync callback method具有异步回调方法的计时器中的异常
【发布时间】:2019-11-14 23:25:34
【问题描述】:

我继承了一些代码,该模式(简化)被用于每秒轮询设备并通过它的 REST api 检查它是否有新数据:

using Newtonsoft.Json;
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Net.Http;

public class Class
{
    private readonly string _iotIPaddress = "123.456.789.0";
    private System.Timers.Timer _timer = null;
    private readonly HttpClient _client = new HttpClient();

    public Constructor()
    {
        _client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(--secrets--);
        _client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
        _timer = new System.Timers.Timer(TimeSpan.FromSeconds(1).TotalMilliseconds);
        _timer.Elapsed += OnTimer;
    }

    private async void OnTimer(object sender, EventArgs e)
    {
        try
        {
            _timer.Stop();

            var success = await ProcessUpdates();
            // Some stuff depending on whether ProcessUpdates succeeded
        }
        catch (Exception ex)
        {
            // Log the exception
        }
        finally
        {
            _timer.Start();
        }
    }

    private async Task<SomeSettingsStruct> ProcessUpdates()
    {
        try
        {
            string json = await _client.GetStringAsync($"http://{_iotIPaddress}/di_value/slot_0"); //Exception is thrown on this line

            var resultdata = JsonConvert.DeserializeObject<SomeSettingsStruct>(json);

            return resultdata;
        }
        catch (Exception e)
        {
            //Log the exception
            return null;
        }
    }
}

而且大多数时候这都很好。但是偶尔连接会滞后(设备通过 wifi 连接)并且 GetStringAsync 会抛出一些预期的异常(呵呵),但有时这似乎会导致代码长时间阻塞。这可能表明网络问题持续时间超过几分钟,但现场人员不认为是这种情况。因此问题一定出在代码的某个地方。

尤其是这个异步回调方法不适合我,计时器无论如何都会在不同的线程上调用它的回调,那么回调中的异步/等待内容会发生什么?那些是在计时器线程上调用的还是他们自己的任务? 这会导致竞争条件,用 HttpClient 做奇怪的事情吗?

这是我在日志中得到的异常:

在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 在 Example.Namespace.Class.d__13.MoveNext() 在 C: \dev\Project\Example\Namespace\Class.cs:103 行

而且由于我们还记录了 FirstChanceExceptions,所以我经常看到这种情况发生之前:

发生异常:由于线程退出或应用程序请求 123.456.789.0:80,I/O 操作已中止

【问题讨论】:

  • 为什么private async SomeSettingsStruct ProcessUpdates() 不像private async Task&lt;SomeSettingsStruct&gt; ProcessUpdates()?您的“结构”中有自定义等待者吗?
  • @JoePhillips OnTimer 是一个事件处理程序,所以 async void 很好,在这里。
  • @Fildor 不是async void,而是async Something。这是无效的语法。
  • 事件处理程序仍然是async void,这意味着没有任何事情等待它完成。如果应用程序终止,它仍将运行,其线程将被中止。
  • @Fildor 哎呀,把 cmets 搞混了。 ` async SomeSettingsStruct ProcessUpdates` 是无效的语法,但我认为这是一个错字,确实是async Task&lt;SomeSettingsStruct&gt; ProcessUpdates

标签: c# .net multithreading http async-await


【解决方案1】:

因此问题一定出在代码的某个地方。

不一定。我认为您的代码没有任何问题。

HttpClient 在发生超时时抛出TaskCanceledException(即当没有响应并放弃等待时)。它不直观,people have complained,但就是这样。

人们have found workarounds 让它抛出一个更好的异常,但我认为你不需要为此烦恼。唯一一次抛出 TaskCanceledException 是如果你传递了 CancellationToken 并且它表示取消。但是你没有通过,所以你可以确定如果它确实抛出了TaskCanceledException,那是因为超时。

您可以延长超时 (_client.Timeout) 超过默认的 100 秒,但如果这是通过不可靠的无线连接进行的,那么这将无济于事。

我赌的是网络问题。您可以使用Wireshark 验证这一点(验证请求已发送,但未收到任何内容)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-10-13
    • 1970-01-01
    • 2018-11-18
    • 2010-12-15
    • 1970-01-01
    • 2017-08-05
    • 2014-04-02
    相关资源
    最近更新 更多