I am trying to create a task tracker in C#. Each task has a unique id/name and a Task that would run "one instance per id can run at the same time.
I created the following class to track tasks
public partial class ProgressTracker : IProgressTracker
{
private ConcurrentDictionary<string, TrackedProgress> _tracker;
public ProgressTracker()
{
_tracker = new ConcurrentDictionary<string, TrackedProgress>();
}
public bool TryStart(string name, Task task)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentNullException(nameof(name));
}
return _tracker.TryAdd(name, new TrackedProgress(task));
}
public bool TryFinish(string name, out int? duration)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentNullException(nameof(name));
}
duration = null;
if (_tracker.TryRemove(name, out TrackedProgress progress))
{
duration = (int)DateTime.Now.Subtract(progress.StartedAt).TotalSeconds;
return true;
}
return false;
}
public bool IsRunning(string name, out int? duration)
{
duration = null;
if (_tracker.TryGetValue(name, out TrackedProgress progress))
{
duration = (int)DateTime.Now.Subtract(progress.StartedAt).TotalSeconds;
return true;
}
return false;
}
public bool TryAbort(string name, out int? duration)
{
duration = null;
if (_tracker.TryGetValue(name, out TrackedProgress progress))
{
if(!progress.Job.IsCanceled && !progress.Job.IsCompleted)
{
// How can I abort progress.Job task
}
duration = (int)DateTime.Now.Subtract(progress.StartedAt).TotalSeconds;
return true;
}
return false;
}
}
Here is the class that contains the data about the tracked task.
public sealed class TrackedProgress
{
internal DateTime StartedAt { get; set; }
public Task Job { get; set; }
public int? Duration { get; set; }
public TrackedProgress(Task job)
{
StartedAt = DateTime.Now;
Job = job;
}
public TrackedProgress(DateTime startedAt, Task job)
{
StartedAt = startedAt;
Job = job;
}
}
Since I am tracking a Task, how can I abort it upon request?
You can add cts to the TrackedProgress class:
public sealed class TrackedProgress
{
internal DateTime StartedAt { get; set; }
public Task Job { get; set; }
public CancellationTokenSource Cts { get; }
public int? Duration { get; set; }
public TrackedProgress(Task job, CancellationTokenSource cts)
{
StartedAt = DateTime.Now;
Job = job;
Cts = cts;
}
public TrackedProgress(DateTime startedAt, Task job, CancellationTokenSource cts)
{
StartedAt = startedAt;
Job = job;
Cts = cts;
}
}
And few changes in ProgressTracker:
public partial class ProgressTracker
{
private ConcurrentDictionary<string, TrackedProgress> _tracker;
public ProgressTracker()
{
_tracker = new ConcurrentDictionary<string, TrackedProgress>();
}
//Here - add the cts
public bool TryStart(string name, Task task, CancellationTokenSource cts)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentNullException(nameof(name));
}
return _tracker.TryAdd(name, new TrackedProgress(task, cts));
}
public bool TryFinish(string name, out int? duration)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentNullException(nameof(name));
}
duration = null;
if (_tracker.TryRemove(name, out TrackedProgress progress))
{
duration = (int)DateTime.Now.Subtract(progress.StartedAt).TotalSeconds;
return true;
}
return false;
}
public bool IsRunning(string name, out int? duration)
{
duration = null;
if (_tracker.TryGetValue(name, out TrackedProgress progress))
{
duration = (int)DateTime.Now.Subtract(progress.StartedAt).TotalSeconds;
return true;
}
return false;
}
public bool TryAbort(string name, out int? duration)
{
duration = null;
if (_tracker.TryGetValue(name, out TrackedProgress progress))
{
if(!progress.Job.IsCanceled && !progress.Job.IsCompleted)
{
// Cancelling the task
progress.Cts.Cancel();
}
duration = (int)DateTime.Now.Subtract(progress.StartedAt).TotalSeconds;
return true;
}
return false;
}
}
Of course you need to make sure you handle cancellation inside the task body too!
After reading the comments, I tweaked my consumer class which import data which may take time. This class will accept a task and a cancellation token. The cancellation token is tracked along with the Task
The tracker class class now looks like this
internal sealed class TrackedProgress
{
public DateTime StartedAt { get; private set; }
public Task Job { get; private set; }
public CancellationTokenSource CancellationToken { get; private set; }
public int? Duration { get; private set; }
public TrackedProgress(Task job, CancellationToken cancellationToken)
: this(DateTime.Now, job, cancellationToken)
{
}
public TrackedProgress(DateTime startedAt, Task job, CancellationToken cancellationToken)
{
StartedAt = startedAt;
Job = job;
CancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
}
public void Abort()
{
CancellationToken?.Cancel();
}
public bool IsRunning()
{
if (Job == null)
{
return false;
}
return !Job.IsCompleted && !Job.IsCanceled;
}
}
The tracker class was modified to this
public partial class ProgressTracker : IProgressTracker
{
private ConcurrentDictionary<string, TrackedProgress> _tracker;
public ProgressTracker()
{
_tracker = new ConcurrentDictionary<string, TrackedProgress>();
}
public bool TryStart(string name, Task task, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentNullException(nameof(name));
}
return _tracker.TryAdd(name, new TrackedProgress(task, cancellationToken));
}
public bool TryFinish(string name, out int? duration)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentNullException(nameof(name));
}
duration = null;
if (_tracker.TryRemove(name, out TrackedProgress progress))
{
if(progress.IsRunning())
{
return false;
}
duration = (int)DateTime.Now.Subtract(progress.StartedAt).TotalSeconds;
return true;
}
return false;
}
public bool IsTracked(string name, out int? duration)
{
duration = null;
if (_tracker.TryGetValue(name, out TrackedProgress progress))
{
duration = (int)DateTime.Now.Subtract(progress.StartedAt).TotalSeconds;
return true;
}
return false;
}
public bool TryAbort(string name, out int? duration)
{
duration = null;
if (_tracker.TryGetValue(name, out TrackedProgress progress))
{
if(progress.IsRunning())
{
progress.Abort();
}
duration = (int)DateTime.Now.Subtract(progress.StartedAt).TotalSeconds;
return true;
}
return false;
}
}
Related
This is a request-response model for websocket messages where you can technically match request id to response id and return the response. In addition to that, there is timeout in case that message is never matched.
The question is do I need to dispose the CancellationTokenSource? If so, where?
var requestManager = new RequestManager();
var input = JsonSerializer.Serialize(new Test { Message = "Hello World" });
var pending = requestManager.AddPendingRequest(x =>
{
var text = x.Deserialize<Test>();
return text?.Message == "Hello World";
}, TimeSpan.FromSeconds(5));
await Task.Delay(4000);
var jsonElement = JsonSerializer.Deserialize<JsonElement>(input);
if (requestManager.TryMatchRequest(jsonElement))
{
Console.WriteLine("Found");
}
public class Test
{
public string Message { get; set; }
}
Code
public class PendingRequest
{
private readonly CancellationTokenSource _cts;
public PendingRequest(Func<JsonElement, bool> handler, TimeSpan timeout)
{
Handler = handler;
Event = new AsyncResetEvent(false, false);
Timeout = timeout;
_cts = new CancellationTokenSource(timeout);
_cts.Token.Register(Fail, false);
}
public Func<JsonElement, bool> Handler { get; }
public JsonElement? Result { get; private set; }
public bool Completed { get; private set; }
public AsyncResetEvent Event { get; }
public TimeSpan Timeout { get; }
public bool CheckData(JsonElement data)
{
if (Handler(data))
{
Result = data;
Completed = true;
Event.Set();
return true;
}
return false;
}
public void Fail()
{
Completed = true;
Event.Set();
}
}
public class RequestManager
{
private readonly IList<PendingRequest> _pendingRequests = new List<PendingRequest>();
public PendingRequest AddPendingRequest(Func<JsonElement, bool> handler, TimeSpan timeout)
{
var pending = new PendingRequest(handler, timeout);
lock (_pendingRequests)
{
_pendingRequests.Add(pending);
}
return pending;
}
public void FailAllPendingRequests()
{
lock (_pendingRequests)
{
foreach (var pendingRequest in _pendingRequests.ToList())
{
pendingRequest.Fail();
_pendingRequests.Remove(pendingRequest);
}
}
}
public IList<PendingRequest> GetAllPendingRequests()
{
lock (_pendingRequests)
{
return _pendingRequests;
}
}
public bool TryMatchRequest(JsonElement tokenData)
{
lock (_pendingRequests)
{
foreach (var request in _pendingRequests)
{
if (!request.CheckData(tokenData))
{
continue;
}
_pendingRequests.Remove(request);
return true;
}
}
return false;
}
public void RemoveTimedOutRequests()
{
PendingRequest[] requests;
lock (_pendingRequests)
{
requests = _pendingRequests.ToArray();
}
foreach (var request in requests.Where(r => r.Completed))
{
lock (_pendingRequests)
{
_pendingRequests.Remove(request);
}
}
}
}
/// <summary>
/// Async auto reset based on Stephen Toub`s implementation
/// https://devblogs.microsoft.com/pfxteam/building-async-coordination-primitives-part-2-asyncautoresetevent/
/// </summary>
public sealed class AsyncResetEvent : IDisposable
{
private static readonly Task<bool> Completed = Task.FromResult(true);
private readonly Queue<TaskCompletionSource<bool>> _waits = new();
private readonly bool _reset;
private bool _signaled;
/// <summary>
/// New AsyncResetEvent
/// </summary>
/// <param name="initialState"></param>
/// <param name="reset"></param>
public AsyncResetEvent(bool initialState = false, bool reset = true)
{
_signaled = initialState;
_reset = reset;
}
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
_waits.Clear();
}
/// <summary>
/// Wait for the AutoResetEvent to be set
/// </summary>
/// <returns></returns>
public Task<bool> WaitAsync(TimeSpan? timeout = null)
{
lock (_waits)
{
if (_signaled)
{
if (_reset)
{
_signaled = false;
}
return Completed;
}
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
if (timeout != null)
{
var cancellationSource = new CancellationTokenSource(timeout.Value);
cancellationSource.Token.Register(() =>
{
tcs.TrySetResult(false);
}, false);
}
_waits.Enqueue(tcs);
return tcs.Task;
}
}
/// <summary>
/// Signal a waiter
/// </summary>
public void Set()
{
lock (_waits)
{
if (!_reset)
{
// Act as ManualResetEvent. Once set keep it signaled and signal everyone who is waiting
_signaled = true;
while (_waits.Count > 0)
{
var toRelease = _waits.Dequeue();
toRelease.TrySetResult(true);
}
}
else
{
// Act as AutoResetEvent. When set signal 1 waiter
if (_waits.Count > 0)
{
var toRelease = _waits.Dequeue();
toRelease.TrySetResult(true);
}
else if (!_signaled)
{
_signaled = true;
}
}
}
}
}
So I wrote a method of code and it pulls from the database correctly (I am using Dapper), but it doesnt pass off to the next method. Can anyone tell me why and what I am doing wrong? Not quite understanding what I am doing wrong here. I have tried a few different ways including below and making and IEnumerable list. I can see the variables in the logger so I know I am pulling them correctly, just not sure why they arent sending to the CheckSite().
public class UptimeService
{
private readonly ILogger<UptimeService> _logger;
private readonly IWebsiteData _webdb;
private readonly IUptimeData _db;
public UptimeService(IWebsiteData webdb, IUptimeData db ,ILogger<UptimeService> logger)
{
_webdb = webdb;
_logger = logger;
_db= db;
}
public class SiteResponse
{
public int Websiteid { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public bool Status { get; set; }
public long ResponseTime { get; set; }
}
public async Task GetAllWebsites()
{
var websites = await _webdb.GetWebsites();
foreach (var website in websites)
{
_logger.LogInformation($"WEBSITE::::: {website.Url} | {website.Name} | {website.Websiteid}");
CheckSite(website.Url, website.Name, website.Websiteid);
}
return ;
}
public SiteResponse CheckSite(string Url, string Name, int Websiteid)
{
var result = new SiteResponse();
var stopwatch = new Stopwatch();
stopwatch.Start();
var client = new HttpClient();
_logger.LogInformation(
$"TEST URL: {result.Url}");
try
{
var checkingResponse = client.GetAsync(Url).Result;
result.Status = checkingResponse.IsSuccessStatusCode &&
checkingResponse.StatusCode == HttpStatusCode.OK;
}
catch
{
result.Status = false;
// offline
}
stopwatch.Stop();
var elapsed = stopwatch.ElapsedMilliseconds;
result.ResponseTime = elapsed;
if (result.Status)
{
// archive record
RecordToDb(result);
}
else
{
_logger.LogInformation(
$"Status is {result.Status}");
}
return result;
}
public async void RecordToDb(SiteResponse response)
{
var newRecord = new UptimeModel
{
Time = DateTime.Now,
Status = response.Status,
ResponseTime = (int)response.ResponseTime,
Websiteid = response.Websiteid,
Name = response.Name,
};
_logger.LogInformation(
$"Trying to Save {response.Name}");
await _db.InsertUptime(newRecord);
}
}
If the result.Url is empty here:
_logger.LogInformation($"TEST URL: {result.Url}");
that's because it's a new instance of SiteResponse() method.
If it is showing as null, you'll need to create constructors on the class. Here is an example:
public class SiteResponse
{
public SiteResponse(){ }
public SiteResponse(string url){
Url = url;
}
public int Websiteid { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public bool Status { get; set; }
public long ResponseTime { get; set; }
}
and then initialize the new one like:
var result = new SiteResponse(Url);
Based on the comments below, I would refactor to something like this.
public class UptimeService
{
private readonly ILogger<UptimeService> _logger;
private readonly IWebsiteData _webdb;
private readonly IUptimeData _db;
public UptimeService(IWebsiteData webdb, IUptimeData db ,ILogger<UptimeService> logger)
{
_webdb = webdb;
_logger = logger;
_db= db;
}
public class SiteResponse
{
public int Websiteid { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public bool Status { get; set; }
public long ResponseTime { get; set; }
}
public async Task GetAllWebsites()
{
var websites = await _webdb.GetWebsites();
foreach (var website in websites)
{
_logger.LogInformation($"WEBSITE::::: {website.Url} | {website.Name} | {website.Websiteid}");
await CheckSite(website);
}
return ;
}
public async Task CheckSite(SiteResponse siteResponse)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
var client = new HttpClient();
_logger.LogInformation(
$"TEST URL: {siteResponse.Url}");
try
{
var checkingResponse = await client.GetAsync(siteResponse.Url);
siteResponse.Status = checkingResponse.IsSuccessStatusCode &&
checkingResponse.StatusCode == HttpStatusCode.OK;
}
catch
{
siteResponse.Status = false;
// offline
}
stopwatch.Stop();
var elapsed = stopwatch.ElapsedMilliseconds;
siteResponse.ResponseTime = elapsed;
if (siteResponse.Status)
{
// archive record
RecordToDb(siteResponse);
}
else
{
_logger.LogInformation(
$"Status is {siteResponse.Status}");
}
return;
}
public async void RecordToDb(SiteResponse response)
{
var newRecord = new UptimeModel
{
Time = DateTime.Now,
Status = response.Status,
ResponseTime = (int)response.ResponseTime,
Websiteid = response.Websiteid,
Name = response.Name,
};
_logger.LogInformation(
$"Trying to Save {response.Name}");
await _db.InsertUptime(newRecord);
}
}
I have the following code, it causes a memory leak.
The problem is the task, when I remove it, everything is fine and the View as well as the ViewModel are GCed. It seems like the Task is keeping a reference to UpdateTimeDate and hence the ViewModel. I tried various things, but none have worked, hoping someone has any idea or explanation why it is the case.
public class HeaderViewModel : Observable, IDisposableAsync
{
public HeaderViewModel (IDateTimeProvider dateTimeProvider)
{
TokenSource = new CancellationTokenSource();
ATask = Task.Run(
async () =>
{
while(!TokenSource.Token.IsCancellationRequested)
{
UpdateTimeData();
await Task.Delay(800);
}
IsDisposed = true;
},
TokenSource.Token);
UpdateTimeData();
void UpdateTimeData()
{
TimeText = dateTimeProvider.Now.ToString("HH:mm:ss");
DateText = dateTimeProvider.Now.ToString("dd.MM.yyyy");
}
}
public CancellationTokenSource TokenSource { get; set; }
public bool IsDisposed { get; set; }
public string TimeText
{
get => Get<string>();
set => Set(value);
}
public string DateText
{
get => Get<string>();
set => Set(value);
}
private Task ATask { get; set; }
public async Task Dispose()
{
TokenSource.Cancel();
while(!IsDisposed)
{
await Task.Delay(50);
}
TokenSource.Dispose();
ATask.Dispose();
ATask = null;
TokenSource = null;
}
}
This is the Timer based solution, it also causes a memory leak:
public class HeaderViewModel : Observable, IDisposableAsync
{
public HeaderViewModel(IDateTimeProvider dateTimeProvider)
{
DateTimeProvider = dateTimeProvider;
ATimer = new Timer(800)
{
Enabled = true
};
UpdateTimeData(this, null);
ATimer.Elapsed += UpdateTimeData;
}
public string TimeText
{
get => Get<string>();
set => Set(value);
}
public string DateText
{
get => Get<string>();
set => Set(value);
}
public bool IsDisposed { get; set; }
private IDateTimeProvider DateTimeProvider { get; }
private Timer ATimer { get; }
public async Task Dispose()
{
ATimer.Stop();
await Task.Delay(1000);
ATimer.Elapsed -= UpdateTimeData;
ATimer.Dispose();
IsDisposed = true;
}
private void UpdateTimeData(object sender, ElapsedEventArgs elapsedEventArgs)
{
TimeText = DateTimeProvider.Now.ToString("HH:mm:ss");
DateText = DateTimeProvider.Now.ToString("dd.MM.yyyy");
}
}
I found a solution. Thanks to keuleJ, he posted the comment that lead me to it. So the Task or Timer is capturing an instance of the ViewModel when you create either of them. The way to prevent it is to make a WeakReference and use that:
public class HeaderViewModel : Observable, IDisposableAsync
{
public HeaderViewModel(IDateTimeProvider dateTimeProvider)
{
DateTimeProvider = dateTimeProvider;
UpdateTimeData();
var weakReference = new WeakReference(this);
Task.Run(
async () =>
{
while(!((HeaderViewModel)weakReference.Target).IsDisposing)
{
((HeaderViewModel)weakReference.Target).UpdateTimeData();
await Task.Delay(800);
}
((HeaderViewModel)weakReference.Target).IsDisposed = true;
});
}
public bool IsDisposed { get; set; }
public string TimeText
{
get => Get<string>();
set => Set(value);
}
public string DateText
{
get => Get<string>();
set => Set(value);
}
private IDateTimeProvider DateTimeProvider { get; }
private bool IsDisposing { get; set; }
public async Task Dispose()
{
IsDisposing = true;
while(!IsDisposed)
{
await Task.Delay(50);
}
}
private void UpdateTimeData()
{
TimeText = DateTimeProvider.Now.ToString("HH:mm:ss");
DateText = DateTimeProvider.Now.ToString("dd.MM.yyyy");
}
}
Note that I also could not make a local variable out of
(HeaderViewModel)weakReference.Target
As soon as I did that some magic seems to happen and the instance would be captured again.
It appears that your Dispose task never returns which is why your object is remaining in memory. I tracked down the issue to the
await Task.Delay(1000)
if you change it per this post https://stackoverflow.com/a/24539937/3084003 it will work
await Task.Delay(1000).ConfigureAwait(false);
I have 50 IMountCmd objects from one or more threads and drop them in a blocking collection. Each is executed and some get results or errors. They are put into a ConcurrentDictionary where I loop for ContainsKey and return the objects. Does this seems thread safe and correct way to process a blocking queue?
public class CmdGetAxisDegrees : IMountCommand
{
public long Id { get; }
public DateTime CreatedUtc { get; private set; }
public bool Successful { get; private set; }
public Exception Exception { get; private set; }
public dynamic Result { get; private set; }
private readonly AxisId _axis;
public CmdGetAxisDegrees(long id, AxisId axis)
{
Id = id;
_axis = axis;
CreatedUtc = HiResDateTime.UtcNow;
Successful = false;
Queues.AddCommand(this);
}
public void Execute(Actions actions)
{
try
{
Result = actions.GetAxisDegrees(_axis);
Successful = true;
}
catch (Exception e)
{
Successful = false;
Exception = e;
}
}
}
private static void ProcessCommandQueue(IMountCommand command)
{
command.Execute(_actions);
if (command.Id > 0)
{
_resultsDictionary.TryAdd(command.Id, command);
}
}
public static IMountCommand GetCommandResult(long id)
{
IMountCommand result;
while (true)
{
if (!_resultsDictionary.ContainsKey(id)) continue;
var success = _resultsDictionary.TryRemove(id, out result);
if (!success)
{
//log
}
break;
}
return result;
}
static Queues()
{
_actions = new Actions();
_resultsDictionary = new ConcurrentDictionary<long, IMountCommand>();
_commandBlockingCollection = new BlockingCollection<IMountCommand>();
Task.Factory.StartNew(() =>
{
foreach (var command in _commandBlockingCollection.GetConsumingEnumerable())
{
ProcessCommandQueue(command);
}
});
}
public interface IMountCommand
{
long Id { get; }
DateTime CreatedUtc { get; }
bool Successful { get; }
Exception Exception { get; }
dynamic Result { get; }
void Execute(Actions actions);
}
Well i have created a job scheduler that has many capabilities; However i really want to use .Net 4.5 Async/Await feature with it in order to wait for a job to finish executing.
-Code
Scheduler.cs
public abstract class Scheduler
{
#region Fields && Properties
private readonly List<Job> _jobs = new List<Job>();
private readonly Random _rand = new Random();
private Job _currentlyExecutingJob;
private Thread _workingThread;
public bool? Parallel { get; private set; }
public DateTimeOffset NextExecutionTime { get; private set; }
public string ID { get; set; }
public abstract Task JobTrigger(Job job);
public abstract void UnobservedException(Exception exception, Job job);
#endregion
#region Ctor
protected Scheduler(bool parallel) { Parallel = parallel; }
#endregion
#region Fluent
public Scheduler Start()
{
if (Equals(_workingThread, null)) {
_workingThread = new Thread(ReviewJobs);
_workingThread.Start();
}
return this;
}
public Scheduler Stop()
{
if (!Equals(_workingThread, null)) {
_workingThread.Abort();
_workingThread = null;
}
return this;
}
#endregion
#region Events
private void ReviewJobs()
{
while (!Equals(_workingThread, null)) {
IEnumerable<Job> jobsToExecute = from job in _jobs
where job.NextExecutionTime <= DateTimeOffset.Now
orderby job.Priority
select job;
if (!jobsToExecute.Any()) {
Thread.Sleep(100);
continue;
}
try {
foreach (Job job in jobsToExecute) {
Job o = _currentlyExecutingJob = job;
if (Parallel != null && (bool)Parallel) {
JobTrigger(o);
} else {
JobTrigger(o).Wait();
}
if (!o.Repeat)
_jobs.Remove(o);
else if (o.Interval != null)
o.NextExecutionTime = DateTimeOffset.Now.Add((TimeSpan)(o.RandomizeExecution
? TimeSpan.FromSeconds(_rand.Next((int)((TimeSpan)o.Interval).TotalSeconds, ((int)((TimeSpan)o.Interval).TotalSeconds + 30)))
: o.Interval));
}
} catch (Exception exception) {
UnobservedException(exception, _currentlyExecutingJob);
} finally {
NextExecutionTime = (from job in _jobs
where job.NextExecutionTime <= DateTimeOffset.Now
orderby job.Priority
select job.NextExecutionTime).FirstOrDefault();
}
}
}
#endregion
#region Helper Methods
public Job GetJob(string id) { return _jobs.Find(job => job.ID == id); }
public Job GetJob(Job job) { return _jobs.Find(x => x == job); }
public IEnumerable<Job> GetAllJobs() { return _jobs; }
public void AddJob(Job job, bool overwrite = false)
{
Job existingJob = GetJob(job);
if (null != existingJob) {
if (overwrite) {
_jobs.RemoveAll(jobs => jobs == existingJob);
_jobs.Add(job);
} else {
_jobs.Add(job);
}
} else {
_jobs.Add(job);
}
}
public bool RemoveJob(string id)
{
Job existingJob = GetJob(id);
if (null != existingJob) {
return _jobs.Remove(existingJob);
}
return false;
}
public bool RemoveJob(Job job)
{
Job existingJob = GetJob(job);
if (null != existingJob) {
return _jobs.Remove(existingJob);
}
return false;
}
public void RemoveAllJobs() { _jobs.RemoveRange(0, _jobs.Count); }
#endregion
}
Job.cs
public class Job
{
public string ID { get; set; }
public TimeSpan Interval { get; private set; }
public DateTimeOffset NextExecutionTime { get; set; }
public int Priority { get; set; }
public bool Repeat { get; private set; }
public bool RandomizeExecution { get; set; }
public object Data { get; set; }
#region Fluent
public Job RunOnceAt(DateTimeOffset executionTime)
{
NextExecutionTime = executionTime;
Repeat = false;
return this;
}
public Job RepeatFrom(DateTimeOffset executionTime, TimeSpan interval)
{
NextExecutionTime = executionTime;
Interval = interval;
Repeat = true;
return this;
}
#endregion
}
-Usage
public class SchedulerUsage : Scheduler
{
public SchedulerUsage(bool parallel) : base(parallel) {
}
public override async Task JobTrigger(Job job)
{
switch (job.ID) {
//Handle Jobs
}
}
public override void UnobservedException(Exception exception, Job job)
{
//Handle Exceptions
}
/// <summary>
/// Example of adding job
/// </summary>
public void ExampleUsage()
{
Job job = new Job
{
ID = "ID",
Data = "ATTACH SOME DATA"
}.RunOnceAt(DateTimeOffset.Now.AddSeconds(7));
//Add the job... [HERE IS WHAT I WANT TO ACHIVE]
/*await*/base.AddJob(job);
//Start the scheduler...
base.Start();
}
}
Question: How to use async/await to await the execution of a job in my implementation above.
NB: I am sorry if my question seems to be a bit complicated but it is very necessary for me so please be patience with me.