如何在Angular+ASP.Net游戏应用中处理离线运行任务?
针对游戏延迟任务的最优解决方案(Angular + ASP.NET)
核心思路
所有延迟操作的状态必须持久化到数据库,避免服务重启导致任务丢失;后台用可靠的任务调度系统触发完成逻辑,前端按需同步状态或接收实时通知。
一、后台实现(ASP.NET)
1. 数据库设计关键点
- 给需要延迟处理的实体(比如
Building、Crop)增加字段:StartTime(DateTime):任务启动时间ExpectedFinishTime(DateTime):预计完成时间Status(枚举:Pending/InProgress/Completed/Failed):当前状态
- 这样即使任务调度服务重启,也能通过扫描数据库重新恢复未完成的任务。
2. 任务调度选择
推荐方案:Hangfire
轻量、易集成,支持持久化任务,适合中小规模游戏:
- 安装NuGet包:
Install-Package Hangfire - 配置启动项(Program.cs):
builder.Services.AddHangfire(config => config.UseSqlServerStorage(builder.Configuration.GetConnectionString("DefaultConnection"))); builder.Services.AddHangfireServer(); - 提交延迟任务:
// 启动建造时,计算完成时间,保存到数据库,同时提交延迟任务 var finishTime = DateTime.UtcNow.AddHours(3); var building = new Building { StartTime = DateTime.UtcNow, ExpectedFinishTime = finishTime, Status = BuildingStatus.InProgress }; _dbContext.Buildings.Add(building); await _dbContext.SaveChangesAsync(); // 提交3小时后执行的任务 BackgroundJob.Schedule(() => _buildingService.CompleteBuilding(building.Id), finishTime); - 完成任务的逻辑:
public async Task CompleteBuilding(int buildingId) { var building = await _dbContext.Buildings.FindAsync(buildingId); if (building == null || building.Status == BuildingStatus.Completed) return; building.Status = BuildingStatus.Completed; // 执行其他逻辑:增加玩家建筑数量、解锁功能等 await _dbContext.SaveChangesAsync(); // 如果需要通知前端,用SignalR推送 await _hubContext.Clients.User(building.PlayerId.ToString()).SendAsync("BuildingCompleted", building); }
备选方案:Quartz.NET
更复杂但功能更强大,适合大规模、高并发场景:
- 核心是创建Job和Trigger,将任务信息持久化到数据库,支持集群部署。
3. 容错机制
- 定期扫描数据库:用Hangfire的RecurringJob或者Quartz的定时任务,每小时扫描所有
InProgress状态的任务,检查ExpectedFinishTime是否已过,若已过则触发完成逻辑,避免调度任务丢失。RecurringJob.AddOrUpdate("recover-failed-tasks", () => _buildingService.RecoverFailedTasks(), Cron.Hourly());
二、前端实现(Angular)
1. 页面加载时同步状态
用户打开游戏页面时,调用API获取当前所有未完成的任务,计算剩余时间:
// building.service.ts getUserBuildings(): Observable<Building[]> { return this.http.get<Building[]>('/api/buildings'); } // building.component.ts ngOnInit(): void { this.buildingService.getUserBuildings().subscribe(buildings => { this.buildings = buildings.map(b => { const remainingMs = new Date(b.expectedFinishTime).getTime() - Date.now(); return { ...b, remainingTime: this.formatRemainingTime(remainingMs) }; }); }); }
2. 实时通知(可选)
用ASP.NET SignalR实现推送,任务完成时前端实时更新:
- 安装SignalR客户端:
npm install @microsoft/signalr - 连接Hub并监听事件:
// signalr.service.ts private hubConnection: HubConnection; connect(): void { this.hubConnection = new HubConnectionBuilder() .withUrl('/buildingHub') .build(); this.hubConnection.start().catch(err => console.error(err)); this.hubConnection.on('BuildingCompleted', (building: Building) => { // 更新页面上的建筑状态 const index = this.buildings.findIndex(b => b.id === building.id); if (index !== -1) { this.buildings[index] = { ...building, remainingTime: '已完成' }; } }); }
3. 避免不必要的轮询
不要用定时器频繁调用API,而是:
- 页面加载时获取一次状态
- 用SignalR接收实时更新
- 用户切换回页面时(通过
window:focus事件)重新获取状态
三、不推荐的方案
- 前端定时器:完全不可靠,用户关闭页面或刷新后任务就会丢失,而且无法保证时间准确性。
- ASP.NET内置的
Task.Delay:服务重启后任务会丢失,不适合持久化的延迟任务。
内容的提问来源于stack exchange,提问作者ThiagoWerneck
相关产品推荐
相关产品推荐

