You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:C# Background Task仅执行一次,无限循环未触发

C#后台任务仅启动时运行一次,循环内断点未触发的排查方案

我实现了一个C# Background Task,用于周期性检查数据是否同步至Google Calendar,但该任务仅在应用启动时运行一次,循环内设置的断点始终未被触发,寻求技术排查方案。

问题代码

protected async override Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        string DbConnectionString = _configuration.GetValue<string>("Site:ConnectionString");

        using (IDbConnection conn = OpenConnection(DbConnectionString))
        {
            List<GoogleAccess> googleCredentials = conn.Query<Domain.GoogleAccess>("Select g.Id, g.CalendarId, g.RefreshToken, convert(nvarchar(36), g.CompanyUserId) as CompanyUserId from [dbo].[googleAccess] g", null, commandType: CommandType.Text).ToList();

            foreach (var credential in googleCredentials.Where(e => e.RefreshToken is not null).ToList())
            {
                var accessToken = RefreshToken(credential.RefreshToken);

                var allReservations = await TurnUp.Admin.Infrastructure.RestAPI.Reservation.GetAll(new Models.RestAPI.Reservation.PostModel.GetAllReservationPostModel() { });

                var covertedList = new List<EventModel>();

                foreach (var reservation in allReservations.result.items.Where(e => e.isCancelled == false).ToList())
                {
                    covertedList.Add(new EventModel
                    {
                        Id = reservation.id.ToString(),
                        Description = reservation.description,
                        Start = new EventDateTime { DateTime = DateTime.Parse(reservation.startTime.ToString()).ToString("yyyy-MM-dd'T'HH:mm:ss.fffK"), TimeZone = "Europe/Zurich" },
                        End = new EventDateTime { DateTime = DateTime.Parse(reservation.endTime.ToString()).ToString("yyyy-MM-dd'T'HH:mm:ss.fffK"), TimeZone = "Europe/Zurich" },
                        Summary = reservation.description
                    });
                }

                var allGoogleEvents = new List<EventModel>();
                RestClient restClient = new RestClient(new Uri("https://www.googleapis.com/calendar/v3/calendars/primary/events"));
                RestRequest restRequest = new RestRequest();

                restRequest.AddQueryParameter("key", $"{ApiKeys}");
                restRequest.AddParameter("maxResults", 2500);
                restRequest.AddParameter("OrderBy", "startTime");
                restRequest.AddParameter("showDeleted", "false");
                restRequest.AddParameter("timeMin", DateTime.UtcNow.AddMonths(-1).ToString("yyyy-MM-dd'T'HH:mm:ss.fffK"));
                restRequest.AddParameter("timeMax", DateTime.UtcNow.AddMonths(6).ToString("yyyy-MM-dd'T'HH:mm:ss.fffK"));
                restRequest.AddHeader("Authorization", "Bearer " + accessToken);
                restRequest.AddHeader("Accept", "application/json");

                try
                {
                    var response = restClient.Get(restRequest);

                    if (response.StatusCode == System.Net.HttpStatusCode.OK)
                    {
                        JObject calendarEvents = JObject.Parse(response.Content);
                        var test = calendarEvents["nextSyncToken"].ToString();
                        allGoogleEvents = calendarEvents["items"].ToObject<List<EventModel>>();
                    }
                }
                catch (Exception ex)
                {
                    throw new Exception("Coulden't connect to google agenda.");
                }

                var newList = covertedList.Except(allGoogleEvents).Concat(allGoogleEvents.Except(covertedList));

                foreach (var ev in newList)
                {
                    var restClientForAddingEvents = new RestClient();
                    var restRequestForAddingEvents = new RestRequest();
                    restClientForAddingEvents.BaseUrl = new System.Uri($"https://www.googleapis.com/calendar/v3/calendars/{credential.CalendarId}/events");

                    EventModel newEvent = new EventModel();

                    newEvent.Start.DateTime = DateTime.Parse(ev.Start.DateTime).ToString("yyyy-MM-dd'T'HH:mm:ss.fffK");
                    newEvent.End.DateTime = DateTime.Parse(ev.End.DateTime).ToString("yyyy-MM-dd'T'HH:mm:ss.fffK");
                    newEvent.Description = ev.Description;
                    newEvent.Id = Guid.NewGuid().ToString().Replace("-", "");

                    var model = JsonConvert.SerializeObject(newEvent, new JsonSerializerSettings
                    {
                        ContractResolver = new CamelCasePropertyNamesContractResolver()
                    });

                    restRequest.AddQueryParameter("key", $"{ApiKeys}");
                    restRequest.AddHeader("Authorization", "Bearer " + accessToken);
                    restRequest.AddHeader("Accept", "application/json");
                    restRequest.AddHeader("Content-Type", "application/json; charset=utf-8");
                    restRequest.AddParameter("application/json", model, ParameterType.RequestBody);

                    try
                    {
                        var restResponse = restClient.Post(restRequest);

                        if (restResponse.StatusCode == System.Net.HttpStatusCode.OK)
                        {

                        }
                    }
                    catch (Exception ex)
                    {
                        throw new Exception("Coulden't Create an event in google agenda.");
                    }
                }
            }
        }
        await Task.Delay(5000, stoppingToken);
    }
}

排查与修复步骤

  • 验证后台任务注册:确认在Program.cs/Startup.cs中通过services.AddHostedService<YourSyncService>()正确注册服务,避免使用一次性实例注册方式,否则任务执行一次后会被回收。
  • 捕获全局未处理异常:后台任务的未捕获异常会直接终止任务且默认无日志输出,需在外层添加异常捕获并记录日志:
    protected async override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("同步任务启动");
        try
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                // 原循环逻辑
                _logger.LogInformation("完成一次同步周期");
                await Task.Delay(5000, stoppingToken);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "同步任务发生未处理异常,已终止");
        }
    }
    
    同时确保主机配置了日志输出(如控制台日志),方便排查异常。
  • 替换同步API调用为异步版本:代码中restClient.Get()和restClient.Post()是同步方法,会阻塞异步任务线程,导致调度器误判任务完成,需替换为异步重载:
    var response = await restClient.GetAsync(restRequest, stoppingToken);
    // ...
    var restResponse = await restClientForAddingEvents.PostAsync(restRequestForAddingEvents, stoppingToken);
    
  • 修复请求对象复用错误:添加事件时误用了外层的restRequest(获取Google事件的请求对象),导致参数混乱引发异常,需改为使用新创建的restRequestForAddingEvents:
    // 替换原restRequest相关代码为:
    restRequestForAddingEvents.AddQueryParameter("key", $"{ApiKeys}");
    restRequestForAddingEvents.AddHeader("Authorization", "Bearer " + accessToken);
    restRequestForAddingEvents.AddHeader("Accept", "application/json");
    restRequestForAddingEvents.AddHeader("Content-Type", "application/json; charset=utf-8");
    restRequestForAddingEvents.AddParameter("application/json", model, ParameterType.RequestBody);
    
  • 修正对象比较逻辑:Except方法依赖EventModel的Equals和GetHashCode实现,若未重写则会按引用比较,导致所有对象都被判定为不同,引发循环执行时间过长。需重写这两个方法:
    public class EventModel
    {
        // 原有属性
        public override bool Equals(object obj)
        {
            if (obj is not EventModel other) return false;
            return Id == other.Id && Start.DateTime == other.Start.DateTime && End.DateTime == other.End.DateTime;
        }
    
        public override int GetHashCode()
        {
            return HashCode.Combine(Id, Start.DateTime, End.DateTime);
        }
    }
    
  • 检查数据库连接与查询:确认OpenConnection方法正确创建并释放连接,若数据库操作阻塞或失败,会导致任务卡在循环内某一步,需添加日志监控数据库操作的执行状态。

内容的提问来源于stack exchange,提问作者Wannes Limbourg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 02:31:13