解决 Hangfire 中使用HttpClient 任务超时问题 TaskCanceledException: The request was canceled due to the configured HttpClient.Timeout of 100 seconds elapsing.
using Hangfire; using LG.WorkerService.Data; using LG.WorkerService.Entities; using LG.WorkerService.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Swashbuckle.AspNetCore.Annotations; using System.Linq.Expressions; // Ensure this namespace is included for Expression usage. namespace LG.WorkerService.Controllers { [Route("api/[controller]")] [ApiController] public class JobController : ControllerBase { private readonly ILogger<JobController> _logger; private readonly JobService _jobService; private readonly IOptions<AppSettings> _appSettings; public JobController(JobService jobService, ILogger<JobController> logger, IOptions<AppSettings> appSettings) { _jobService = jobService; _logger = logger; _appSettings = appSettings; } [HttpGet(nameof(AddJobToWorkerPool))] [SwaggerOperation(Summary = "将全部任务添加到计划队列", Description = "将全部任务添加到计划队列")] public async Task<ActionResult> AddJobToWorkerPool() { _logger.LogInformation("开始添加任务到计划队列..."); List<JobInfo>? list = new List<JobInfo>(); try { list = await _jobService.GetJobInfosAsync(); } catch (Exception ex) { _logger.LogError(ex, "获取任务列表失败"); return StatusCode(500, $"获取任务列表失败:{ex.Message} {ex.InnerException?.Message}"); } if (list == null || !list.Any()) { return NotFound("必须在表【JobInfo】中添加需要执行的任务"); } RecurringJobOptions options = new RecurringJobOptions(); options.TimeZone = TimeZoneInfo.Local;//默认为世界时区,相差8个小时,必须改为本地时区 string uri = string.Empty; foreach (var job in list) { RecurringJob.RemoveIfExists(job.JobId); uri = job.GetFullUri(_appSettings.Value.BaseAddress); RecurringJob.AddOrUpdate<CreateJobService>(job.Id.ToString(), j => j.CallApiEndpointAsync(uri, job), job.CronExpressions, options); } _logger.LogInformation($"已成功添加 {list.Count} 个任务到计划队列中。"); _logger.LogInformation($"已添加任务列表 JobIds:\r\n {string.Join(',', list.Select(m => m.JobId).ToArray())}"); return Ok(); } //private Expression<Func<Task>> CreateJobExpression(JobInfo job) //{ // var httpClient = _httpClient.CreateClient(ConstHelper.HttpClientName); // httpClient.Timeout = Timeout.InfiniteTimeSpan; // 设置为无限超时,避免超时错误 // string uri = job.GetFullUri(_appSettings.Value.BaseAddress); // if (job.QuestMethod.Equals("Post", StringComparison.OrdinalIgnoreCase)) // { // return () => httpClient.PostAsync(uri, null); // } // else // { // return () => _httpClient.GetStringAsync(uri); // } //} [HttpPost(nameof(AddJobToWorkerPoolByIds))] [SwaggerOperation(Summary = "添加指定任务Id到计划队列", Description = "添加指定任务Id到计划队列")] public async Task<ActionResult> AddJobToWorkerPoolByIds(List<int> ids) { if (ids == null || ids.Any() == false) { return BadRequest("参数 ids 不能为空"); } _logger.LogInformation("开始添加任务到计划队列..."); List<JobInfo>? list = new List<JobInfo>(); try { list = await _jobService.GetJobInfosAsync(ids); } catch (Exception ex) { _logger.LogError(ex, "获取任务列表失败"); return StatusCode(500, $"获取任务列表失败:{ex.Message} {ex.InnerException?.Message}"); } if (list == null || !list.Any()) { return NotFound("必须在表【JobInfo】中添加需要执行的任务"); } RecurringJobOptions options = new RecurringJobOptions(); options.TimeZone = TimeZoneInfo.Local;//默认为世界时区,相差8个小时,必须改为本地时区 string uri = string.Empty; foreach (var job in list) { RecurringJob.RemoveIfExists(job.JobId); //RecurringJob.AddOrUpdate(job.JobId, CreateJobExpression(job), job.CronExpressions, options); uri = job.GetFullUri(_appSettings.Value.BaseAddress); RecurringJob.AddOrUpdate<CreateJobService>(job.Id.ToString(), j => j.CallApiEndpointAsync(uri, job), job.CronExpressions, options); } _logger.LogInformation($"已成功添加 {list.Count} 个任务到计划队列中。"); _logger.LogInformation($"已添加任务列表 JobIds:\r\n {string.Join(',', list.Select(m => m.JobId).ToArray())}"); return Ok(); } [HttpPost(nameof(AddTask))] [SwaggerOperation(Summary = "新添调度任务到数据库", Description = "新添调度任务到数据库")] public async Task<ActionResult> AddTask(JobInfoDTO jobInfoDTO) { JobInfo jobInfo = GenerateJobInfo(jobInfoDTO); var result = await _jobService.AddJobInfoAsync(jobInfo); return Ok(result); } private JobInfo GenerateJobInfo(JobInfoDTO jobInfoDTO) { JobInfo job = new JobInfo { JobId = jobInfoDTO.JobId, JobCategory = jobInfoDTO.JobCategory, Uri = jobInfoDTO.Uri, Remark = jobInfoDTO.Remark, QuestMethod = jobInfoDTO.QuestMethod, CronExpressions = jobInfoDTO.CronExpressions, UpdateTime = DateTime.Now }; return job; } } }
using Hangfire; using LG.WorkerService.Entities; using System.Linq.Expressions; using System.Net.Http; namespace LG.WorkerService.Services { public class CreateJobService { private readonly HttpClient _httpClient; public CreateJobService(HttpClient httpClient) { _httpClient = httpClient; _httpClient.Timeout = TimeSpan.FromMinutes(10); } public Task<string> CallApiEndpointAsync(string uri, JobInfo job) { string result = string.Empty; if (job.QuestMethod.Equals("Post", StringComparison.OrdinalIgnoreCase)) { //return _httpClient.PostAsync(uri, null); throw new NotImplementedException("POST method is not implemented yet."); } else { return _httpClient.GetStringAsync(uri); } } } }
using Hangfire; using LG.WorkerService; using LG.WorkerService.Data; using LG.WorkerService.Entities; using LG.WorkerService.Services; using Microsoft.OpenApi.Models; var builder = WebApplication.CreateBuilder(args); var services = builder.Services; string? connectionString = builder.Configuration.GetConnectionString("HangfireConnection"); //Configuration.GetConnectionString("DefaultConnection"); SqlHelper.ConnectionString = connectionString; services.AddHttpClient(ConstHelper.HttpClientName, client => { client.Timeout = TimeSpan.FromMinutes(30); }); builder.Services.AddHttpClient<CreateJobService>(); //Dapper services.AddSingleton<DapperContext>(); services.AddScoped<JobService>(); //注入配置文件映射到实例 builder.Services.AddOptions() .Configure<AppSettings>(options => builder.Configuration.GetSection("AppSettings").Bind(options)); // Add services to the container. //Add Hangfire services. services.AddHangfire(configuration => configuration .SetDataCompatibilityLevel(CompatibilityLevel.Version_180) .UseSimpleAssemblyNameTypeSerializer() .UseRecommendedSerializerSettings() .UseSqlServerStorage(builder.Configuration.GetConnectionString("HangfireConnection")) ); // Add the processing server as IHostedService services.AddHangfireServer(options => { //HeartbeatInterval (心跳间隔) options.HeartbeatInterval = TimeSpan.FromSeconds(40); //options.ServerTimeout = TimeSpan.FromMinutes(10);//服务器超时 //options.SchedulePollingInterval = TimeSpan.FromSeconds(50);//调度轮询间隔 //options.StopTimeout = TimeSpan.FromMinutes(10);//停止超时 服务器关闭时,等待当前正在执行的任务完成的超时时间 //options.ShutdownTimeout = TimeSpan.FromMinutes(12);//关闭超时 }); builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "任务调度服务API", Version = "v1" }); c.EnableAnnotations(); }); var app = builder.Build(); app.UseHangfireDashboard();//http://localhost:5000/hangfire // Configure the HTTP request pipeline. //if (app.Environment.IsDevelopment()) { app.UseSwagger(); //app.UseSwaggerUI(); app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "任务调度服务API")); } app.UseAuthorization(); app.MapControllers(); app.Run();
参考:https://blog.csdn.net/hefeng_aspnet/article/details/143199033
浙公网安备 33010602011771号