SemaphoreSlim类中的WaitHandle.WaitOne()方法无法正常工作

时间:2018-08-09 15:08:32

标签: c# asp.net-core .net-standard-2.0

我的处境很复杂,但我会尝试将其简化,仅让您知道重要的细节。我正在尝试实施基于任务的作业处理。这是该课程的课程:

{
  "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方法。

1 个答案:

答案 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();

hashCode1hashCode2相同,与hashCode3hashCode4相同(因为Singleton),但是hashCode1 / hashCode2不相同与hashCode3 / hashCode4相同(因为服务提供商不同)。

真正的解决方法可能是对IAccessInfoStore进行某些检查,该检查将在内部阻塞,直到作业第一次完成。

干杯!