I have a complex situation but I will try to short it out and let only know for important details. I am trying to implement a task-based job handling. here is the class for that:
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);
}
}
Below is the scheduler class :
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;
}
}
Inside of this library I have a service like below: So the idea of this service is only to fetch some data at the dictionary called _accessInfos
and its async method. You can see at the constructor I already add the job to fetch the data.
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;
}
}
All of this code is inside another library that I have referenced into my ASP.NET Core 2.1 project. On the startup class I have a call like this:
//adding services
...
services.AddScoped<IScheduler, Scheduler>();
services.AddScoped<IAccessInfoStore, AccessInfoStore>();
var accessInfoStore = services.BuildServiceProvider().GetService<IAccessInfoStore>();
accessInfoStore.FetchJob.WaitHandle.WaitOne();
At the first time WaitOne()
method does not work so the data are not loaded(_accessInfos
is empty) but if I refresh the page again I can see the data loaded(_accessInfos
is not empty but has data). So, as far as I know WaitOne()
method is to block thread execution until my job is completed.
Does anybody know why WaitOne()
method does not work properly or what I might be doing wrong ?
EDIT 1:
Scheduler
only stores all IJob
-s into a concurrent dictionary in order to get them later if needed mainly for showing them in a health page. Then every time we insert a new TaskBasedJob
in dictionary the constructor will be executed and at the end we use a Timer
to re-execute the job later after some interval, but in order to make this thread-safe I use SemaphoreSlim class and from there I expose WaitHandle
. This is only for those rare cases I need to turn a method from async to sync. Because in general I would not use this because the job will execute in async manner for normal cases.
What I expect - The WaitOne()
should stop execution of current thread and wait until my scheduled job is executed and then continue on executing current thread. In my case current thread is the one running Configure
method at StartUp
class.