代码之家  ›  专栏  ›  技术社区  ›  Rey

信号量slim类中的WaitHandle.WaitOne()方法无法正常工作

  •  0
  • Rey  · 技术社区  · 8 年前

    我有一个复杂的情况,但我会尽量简短,只让重要的细节。我正在尝试实现基于任务的工作处理。这是这门课的内容:

    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 以及它的异步方法。您可以在构造函数中看到我已经添加了获取数据的作业。

    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;
            }
        }
    

    所有这些代码都在另一个库中,我在我的ASP.NET Core 2.1项目中引用了这个库。在创业班我接到这样的电话:

    //adding services
    ...
    services.AddScoped<IScheduler, Scheduler>();
    services.AddScoped<IAccessInfoStore, AccessInfoStore>();
    
    var accessInfoStore = services.BuildServiceProvider().GetService<IAccessInfoStore>();
    
    accessInfoStore.FetchJob.WaitHandle.WaitOne();
    

    第一次 WaitOne() 方法不起作用,因此不加载数据( _访问信息 是空的),但是如果我再次刷新页面,我可以看到加载的数据( _访问信息 不是空的,但有数据)。据我所知 等等() 方法是在我的作业完成之前阻止线程执行。

    有人知道为什么吗 等等() 方法不正常或我可能做错了什么?

    编辑1:

    Scheduler 只存储所有 IJob -如果主要是为了在运行状况页中显示它们,则将它们放入并发字典中以便以后获取它们。每次我们插入一个新的 TaskBasedJob 在字典中,构造函数将被执行,最后我们使用 Timer 为了在一段时间后重新执行这个作业,但是为了使这个线程安全,我使用了SemaphoreSlim类,并从那里公开 WaitHandle . 这只适用于我需要将方法从异步转换为同步的少数情况。因为一般情况下,我不会使用它,因为在正常情况下,作业将以异步方式执行。

    我期望的是 等等() 应该停止当前线程的执行并等待我的计划作业被执行,然后继续执行当前线程。在我的例子中,当前线程是运行的线程 Configure 方法 StartUp 上课。

    1 回复  |  直到 8 年前
        1
  •  1
  •   FrankyBoy    8 年前

    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 (因为辛格尔顿),但是 哈希代码1 / 哈希码2 哈希代码3 / 哈希码4 (因为不同的服务提供商)。

    真正的解决方法可能是在IAccessInfoStore中签入一些代码,这些代码将在内部阻塞,直到作业第一次完成为止。

    干杯!