学海无涯

导航

解决 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

posted on 2025-11-24 15:29  宁静致远.  阅读(3)  评论(0)    收藏  举报