我的处境很复杂,但我会尝试将其简化,仅让您知道重要的细节。我正在尝试实施基于任务的作业处理。这是该课程的课程:
{
"name": "my project",
"version": "0.0.1",
"private": true,
"scripts": {
"build": "react-scripts build",
"start": "npm run gulp -- --production && sequelize db:migrate --config=config/migrations.json --env=production && pm2 startOrReload config/pm2/production.json",
"dev": "npm run gulp dev -- --development",
"gulp": "gulp",
"prestart": "npm install",
"startStaging": "npm install && npm run gulp -- --staging && sequelize db:migrate --config=config/migrations.json --env=staging && pm2 startOrReload config/pm2/staging.json",
"stop": "pm2 stop config/pm2/production.json",
"stopStaging": "pm2 stop config/pm2/staging.json",
"deploy": "git pull && npm start",
"deployStaging": "git pull && npm run startStaging",
},
"dependencies": {
"@babel/runtime": "7.0.0-beta.55",
"@material-ui/core": "1.4.1",
"@material-ui/icons": "2.0.0",
"@types/googlemaps": "3.30.11",
"@types/markerclustererplus": "2.1.33",
"ajv": "6.5.2",
"async": "^2.6.0",
"babel-polyfill": "^6.26.0",
"body-parser": "^1.18.2",
"bunyan": "^1.8.12",
"chance": "^1.0.12",
"chartist": "0.10.1",
"cheerio": "^0.20.0",
"classnames": "2.2.6",
"compression": "^1.7.1",
"connect-flash": "^0.1.1",
"continuation-local-storage": "^3.2.1",
"cookie-parser": "^1.3.3",
"csv-parse": "^2.2.0",
"del": "^2.2.2",
"express": "^4.16.2",
"express-brute": "^1.0.1",
"express-brute-redis": "0.0.1",
"express-jwt": "^3.4.0",
"geolib": "^2.0.24",
"glob": "^6.0.4",
"helmet": "^2.3.0",
"inky": "^1.3.7",
"js-cookie": "^2.2.0",
"jsonwebtoken": "^7.4.3",
"juice": "^2.0.0",
"lodash": "^4.17.4",
"material-ui": "^0.19.3",
"method-override": "^2.3.10",
"minimist": "^1.2.0",
"moment": "^2.19.3",
"moment-timezone": "^0.5.14",
"morgan": "^1.9.0",
"multi-glob": "^1.0.1",
"mysql": "^2.15.0",
"node-fetch": "^2.1.2",
"node-sass": "^3.13.1",
"node-schedule": "^1.2.5",
"nunjucks": "^2.5.2",
"perfect-scrollbar": "1.4.0",
"prop-types": "^15.6.0",
"react": "^16.2.0",
"react-chartist": "0.13.1",
"react-dom": "16.4.1",
"react-dropzone": "^4.2.3",
"react-google-maps": "9.4.5",
"react-redux": "^5.0.6",
"react-router-dom": "4.3.1",
"react-scripts": "1.1.4",
"react-swipeable-views": "0.12.15",
"redux": "^3.7.2",
"redux-thunk": "^2.3.0",
"request": "^2.83.0",
"run-sequence": "^1.2.2",
"sequelize": "^3.31.0",
"serve-favicon": "^2.4.5",
"slugify": "^1.3.0",
"uuid": "^3.1.0",
"validator": "^9.2.0",
"vinyl-buffer": "^1.0.0"
},
}
下面是调度程序类:
internal class TaskBasedJob : IJob
{
public WaitHandle WaitHandle { get; }
public JobStatus Status { get; private set; }
public TaskBasedJob(Func<Task<JobStatus>> action, TimeSpan interval, TimeSpan delay)
{
Status = JobStatus.NotExecuted;
var semaphore = new SemaphoreSlim(0, 1);
WaitHandle = semaphore.AvailableWaitHandle;
_timer = new Timer(async x =>
{
// return to prevent duplicate executions
// Semaphore starts locked so WaitHandle works properly
if (semaphore.CurrentCount == 0 && Status != JobStatus.NotExecuted)
{
return;
Status = JobStatus.Failure;
}
if(Status != JobStatus.NotExecuted)
await semaphore.WaitAsync();
try
{
await action();
}
finally
{
semaphore.Release();
}
}, null, delay, interval);
}
}
在此库中,我有一个类似于以下的服务:因此,该服务的想法只是在名为internal class Scheduler : IScheduler
{
private readonly ILogger _logger;
private readonly ConcurrentDictionary<string, IJob> _timers = new ConcurrentDictionary<string, IJob>();
public Scheduler(ILogger logger)
{
_logger = logger;
}
public IJob ScheduleAsync(string jobName, Func<Task<JobStatus>> action, TimeSpan interval, TimeSpan delay = default(TimeSpan))
{
if (!_timers.ContainsKey(jobName))
{
lock (_timers)
{
if (!_timers.ContainsKey(jobName))
_timers.TryAdd(jobName, new TaskBasedJob(jobName, action, interval, delay, _logger));
}
}
return _timers[jobName];
}
public IReadOnlyDictionary<string, IJob> GetJobs()
{
return _timers;
}
}
的字典及其异步方法中获取一些数据。您可以在构造函数中看到我已经添加了作业以获取数据。
_accessInfos
所有这些代码都位于另一个库中,该库已在ASP.NET Core 2.1项目中引用。在启动类上,我有这样的电话:
internal class AccessInfoStore : IAccessInfoStore
{
private readonly ILogger _logger;
private readonly Func<HttpClient> _httpClientFunc;
private volatile Dictionary<string, IAccessInfo> _accessInfos;
private readonly IScheduler _scheduler;
private static string JobName = "AccessInfoProviderJob";
public AccessInfoStore(IScheduler scheduler, ILogger logger, Func<HttpClient> httpClientFunc)
{
_accessInfos = new Dictionary<string, IAccessInfo>();
_config = config;
_logger = logger;
_httpClientFunc = httpClientFunc;
_scheduler = scheduler;
scheduler.ScheduleAsync(JobName, FetchAccessInfos, TimeSpan.FromMinutes(1));
}
public IJob FetchJob => _scheduler.GetJobs()[JobName];
private async Task<JobStatus> FetchAccessInfos()
{
using (var client = _httpClientFunc())
{
accessIds = //calling a webservice
_accessInfos = accessIds;
return JobStatus.Success;
}
}
第一次使用//adding services
...
services.AddScoped<IScheduler, Scheduler>();
services.AddScoped<IAccessInfoStore, AccessInfoStore>();
var accessInfoStore = services.BuildServiceProvider().GetService<IAccessInfoStore>();
accessInfoStore.FetchJob.WaitHandle.WaitOne();
方法无效,因此未加载数据(WaitOne()
为空),但是如果我再次刷新页面,则可以看到已加载的数据(_accessInfos
不为空,但有数据)。因此,据我所知,_accessInfos
方法是阻塞线程执行,直到我的工作完成为止。
有人知道为什么WaitOne()
方法不能正常工作或我做错了什么吗?
编辑1:
WaitOne()
仅将所有Scheduler
-存储到并发字典中,以便以后(主要是在健康页中显示它们)需要时得到它们。然后,每次我们在字典中插入新的IJob
时,构造函数都会被执行,最后,我们使用TaskBasedJob
在一段时间后稍后重新执行作业,但是为了使该线程-安全,我使用SemaphoreSlim类,然后从那里暴露Timer
。仅在少数情况下,我需要将方法从异步转换为同步。因为通常我不会使用它,因为在正常情况下该作业将以异步方式执行。
我期望的是-WaitHandle
应该停止当前线程的执行,并等待直到执行计划的作业,然后继续执行当前线程。就我而言,当前线程是WaitOne()
类中正在运行的Configure
方法。
答案 0 :(得分:1)
Rajmond的同事在这里。我弄清楚了我们的问题。基本上,等待工作正常,依此类推。我们的问题很简单,如果您执行IServiceCollection.BuildServiceProvider()
,则每次都会获得一个不同的作用域(因此,即使使用Singleton实例也将创建一个不同的对象)。简单的方法可以尝试:
var serviceProvider1 = services.BuildServiceProvider();
var hashCode1 = serviceProvider1.GetService<IAccessInfoStore>().GetHashCode();
var hashCode2 = serviceProvider1.GetService<IAccessInfoStore>().GetHashCode();
var serviceProvider2 = services.BuildServiceProvider();
var hashCode3 = serviceProvider2.GetService<IAccessInfoStore>().GetHashCode();
var hashCode4 = serviceProvider2.GetService<IAccessInfoStore>().GetHashCode();
hashCode1
和hashCode2
相同,与hashCode3
和hashCode4
相同(因为Singleton),但是hashCode1
/ hashCode2
不相同与hashCode3
/ hashCode4
相同(因为服务提供商不同)。
真正的解决方法可能是对IAccessInfoStore进行某些检查,该检查将在内部阻塞,直到作业第一次完成。
干杯!