求助: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
相关产品推荐
相关产品推荐

