Scheduler built with observables v2 (follow-up)
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
3
down vote
favorite
It would probably be too easy if the old Scheduler
worked as intended being that simple. But an eye-openig review showed that from time to time it'll miss one second due to 14ms timer incaccuracy. This of course would have bad implications on the schedules (they wouldn't fire if their second was skipped).
I spent sometime experimenting with various solutions to fix the missing second bug and I think I found a solution that works nicely. I didn't want it to be too complex, I'm not (currently) writing any systems essential for life. It just should make sure nothing is missing. Without it the observable will accumulate all published values and the first subscriber will recieve them all. We don't want this to happen because we are interested at what's now and now in the history.
So how does the Scheduler
work? I turned it into a hot observable with Publish
+ Connect
. I want it to be ticking regardles if there is anyone listening.
The new Scheduler
also can check how many jobs are already running and decline starting a new one.
How did I fix the missing second issue? The Scheduler
is publishing timestamps the same way it did before but now it additionally checks if the difference between the last and the current timestamp is greater then 1s
. If it is, it publishes the missing second before publishing the current one. This is achieved by switching from Select
to SelectMany
. Two intervals published virtually at the same moment don't really matter. More important is to guarantee that nothing is left out and that jobs at any second can be triggered. I think it's extremely unlikely that this would cause any issues ever.
public class Scheduler : IDisposable
public const int UnlimitedJobParallelism = -1;
private const int ZeroJobCounter = 0;
private IConnectableObservable<DateTime> _scheduler;
private IDisposable _disconnect;
private readonly ConcurrentDictionary<Job, int> _jobCounters;
public Scheduler(TimeSpan period, IDateTime dateTime)
var lastTimestamp = dateTime.Now();
_scheduler =
Observable
.Interval(period)
.SelectMany(_ =>
// If we missed one second due to time inaccuracy,
// this makes sure to publish the missing second too
// so that all jobs at that second can also be triggered.
var now = dateTime.Now();
var timestampDifference = now.Second - lastTimestamp.Second;
var timestamps =
Enumerable
// When starting the scheduler it might occur
// that the difference is a negative number (-59).
// If this is the case, make it 1.
.Range(1, timestampDifference < 0 ? 1 : timestampDifference)
.Select(second => lastTimestamp.AddSeconds(second));
lastTimestamp = now;
return timestamps.Dump();
)
// Share the actual data values and not just the observable instance.
.Publish();
// Turn this into a hot observable and start ticking
// regardless if there is anything listening.
_disconnect = _scheduler.Connect();
_jobCounters = new ConcurrentDictionary<Job, int>();
public Scheduler()
: this(TimeSpan.FromSeconds(1), new LocalDateTime())
public IDisposable Schedule(Job job)
var cronExpression = CronExpression.Parse(job.CronExpression);
var unschedule =
_scheduler
.Where(cronExpression.Contains)
.Subscribe(async timestamp =>
if (CanExecute(job))
_jobCounters.AddOrUpdate(job, 1, (j, c) => c + 1);
await job
.ExecuteAsync(timestamp)
.ContinueWith(_ =>
_jobCounters
.AddOrUpdate(job, 1, (j, c) => c - 1)
);
else
$"'job.Name' is alredy running!".Dump();
);
return Disposable.Create(() =>
_jobCounters.TryRemove(job, out var _);
unschedule.Dispose();
);
private bool CanExecute(Job job)
// Reversed the second condition to make the left side consistent.
return
job.MaxDegreeOfParallelism == UnlimitedJobParallelism
public void Dispose()
// Stop ticking.
_disconnect.Dispose();
There are also two new helpers. An extensions for the scheduler and the Job
class.
public static class SchedulerExtensions
public static IDisposable Schedule(this Scheduler scheduler, string cronExpression, Func<DateTime, Task> execute, int maxDegreeOfParallelism = 1)
return scheduler.Schedule(new Job
CronExpression = cronExpression,
ExecuteAsync = execute,
MaxDegreeOfParallelism = maxDegreeOfParallelism
);
public class Job
public string Name get; set;
public string CronExpression get; set;
public Func<DateTime, Task> ExecuteAsync get; set;
public int MaxDegreeOfParallelism get; set; = 1;
Example
The usage didn't change much, just the signatures.
void Main()
var scheduler = new Scheduler();
// Let the scheduler tick for a moment...
Thread.Sleep(3300);
scheduler.Schedule("0/1 * * * * * *", async schedule =>
Console.WriteLine($"DEBUG: schedule - schedule:ss.fff [Thread.CurrentThread.ManagedThreadId]");
await Task.Delay(1100);
, maxDegreeOfParallelism: Scheduler.UnlimitedJobParallelism);
scheduler.Schedule("0/2 * * * * * *", async schedule =>
Console.WriteLine($"ACTION-1: schedule - schedule:ss.fff");
await Task.Delay(1500);
, maxDegreeOfParallelism: Scheduler.UnlimitedJobParallelism);
scheduler.Schedule(new Job
Name = "Test-job",
CronExpression = "0/3 * * * * * *",
ExecuteAsync = async schedule =>
Console.WriteLine($"ACTION-2: schedule - schedule:ss.fff");
await Task.Delay(4000);
,
MaxDegreeOfParallelism = 1
);
c# timer async-await observer-pattern scheduled-tasks
add a comment |Â
up vote
3
down vote
favorite
It would probably be too easy if the old Scheduler
worked as intended being that simple. But an eye-openig review showed that from time to time it'll miss one second due to 14ms timer incaccuracy. This of course would have bad implications on the schedules (they wouldn't fire if their second was skipped).
I spent sometime experimenting with various solutions to fix the missing second bug and I think I found a solution that works nicely. I didn't want it to be too complex, I'm not (currently) writing any systems essential for life. It just should make sure nothing is missing. Without it the observable will accumulate all published values and the first subscriber will recieve them all. We don't want this to happen because we are interested at what's now and now in the history.
So how does the Scheduler
work? I turned it into a hot observable with Publish
+ Connect
. I want it to be ticking regardles if there is anyone listening.
The new Scheduler
also can check how many jobs are already running and decline starting a new one.
How did I fix the missing second issue? The Scheduler
is publishing timestamps the same way it did before but now it additionally checks if the difference between the last and the current timestamp is greater then 1s
. If it is, it publishes the missing second before publishing the current one. This is achieved by switching from Select
to SelectMany
. Two intervals published virtually at the same moment don't really matter. More important is to guarantee that nothing is left out and that jobs at any second can be triggered. I think it's extremely unlikely that this would cause any issues ever.
public class Scheduler : IDisposable
public const int UnlimitedJobParallelism = -1;
private const int ZeroJobCounter = 0;
private IConnectableObservable<DateTime> _scheduler;
private IDisposable _disconnect;
private readonly ConcurrentDictionary<Job, int> _jobCounters;
public Scheduler(TimeSpan period, IDateTime dateTime)
var lastTimestamp = dateTime.Now();
_scheduler =
Observable
.Interval(period)
.SelectMany(_ =>
// If we missed one second due to time inaccuracy,
// this makes sure to publish the missing second too
// so that all jobs at that second can also be triggered.
var now = dateTime.Now();
var timestampDifference = now.Second - lastTimestamp.Second;
var timestamps =
Enumerable
// When starting the scheduler it might occur
// that the difference is a negative number (-59).
// If this is the case, make it 1.
.Range(1, timestampDifference < 0 ? 1 : timestampDifference)
.Select(second => lastTimestamp.AddSeconds(second));
lastTimestamp = now;
return timestamps.Dump();
)
// Share the actual data values and not just the observable instance.
.Publish();
// Turn this into a hot observable and start ticking
// regardless if there is anything listening.
_disconnect = _scheduler.Connect();
_jobCounters = new ConcurrentDictionary<Job, int>();
public Scheduler()
: this(TimeSpan.FromSeconds(1), new LocalDateTime())
public IDisposable Schedule(Job job)
var cronExpression = CronExpression.Parse(job.CronExpression);
var unschedule =
_scheduler
.Where(cronExpression.Contains)
.Subscribe(async timestamp =>
if (CanExecute(job))
_jobCounters.AddOrUpdate(job, 1, (j, c) => c + 1);
await job
.ExecuteAsync(timestamp)
.ContinueWith(_ =>
_jobCounters
.AddOrUpdate(job, 1, (j, c) => c - 1)
);
else
$"'job.Name' is alredy running!".Dump();
);
return Disposable.Create(() =>
_jobCounters.TryRemove(job, out var _);
unschedule.Dispose();
);
private bool CanExecute(Job job)
// Reversed the second condition to make the left side consistent.
return
job.MaxDegreeOfParallelism == UnlimitedJobParallelism
public void Dispose()
// Stop ticking.
_disconnect.Dispose();
There are also two new helpers. An extensions for the scheduler and the Job
class.
public static class SchedulerExtensions
public static IDisposable Schedule(this Scheduler scheduler, string cronExpression, Func<DateTime, Task> execute, int maxDegreeOfParallelism = 1)
return scheduler.Schedule(new Job
CronExpression = cronExpression,
ExecuteAsync = execute,
MaxDegreeOfParallelism = maxDegreeOfParallelism
);
public class Job
public string Name get; set;
public string CronExpression get; set;
public Func<DateTime, Task> ExecuteAsync get; set;
public int MaxDegreeOfParallelism get; set; = 1;
Example
The usage didn't change much, just the signatures.
void Main()
var scheduler = new Scheduler();
// Let the scheduler tick for a moment...
Thread.Sleep(3300);
scheduler.Schedule("0/1 * * * * * *", async schedule =>
Console.WriteLine($"DEBUG: schedule - schedule:ss.fff [Thread.CurrentThread.ManagedThreadId]");
await Task.Delay(1100);
, maxDegreeOfParallelism: Scheduler.UnlimitedJobParallelism);
scheduler.Schedule("0/2 * * * * * *", async schedule =>
Console.WriteLine($"ACTION-1: schedule - schedule:ss.fff");
await Task.Delay(1500);
, maxDegreeOfParallelism: Scheduler.UnlimitedJobParallelism);
scheduler.Schedule(new Job
Name = "Test-job",
CronExpression = "0/3 * * * * * *",
ExecuteAsync = async schedule =>
Console.WriteLine($"ACTION-2: schedule - schedule:ss.fff");
await Task.Delay(4000);
,
MaxDegreeOfParallelism = 1
);
c# timer async-await observer-pattern scheduled-tasks
I'd like to add that theJob
validation will be done later with my Simple object validator.
â t3chb0t
Apr 15 at 13:34
1
I have tried this, and it seems to work very well. One comment: because you're awaiting job.ExecuteAsync(...), you don't have to use Task.ContinueWith(...). In an attempt to dive into the Reactive namespace and in an attempt to "solve" the millisecond problem, I have elaborated on you work. Do you mind, if I post a question heavily cannibalising on you ideas?
â Henrik Hansen
Apr 16 at 7:34
@HenrikHansen not at all. Please do. I'd be happy to see your code and maybe learn something new from it and incorporate it into my solution ;-)
â t3chb0t
Apr 16 at 7:37
add a comment |Â
up vote
3
down vote
favorite
up vote
3
down vote
favorite
It would probably be too easy if the old Scheduler
worked as intended being that simple. But an eye-openig review showed that from time to time it'll miss one second due to 14ms timer incaccuracy. This of course would have bad implications on the schedules (they wouldn't fire if their second was skipped).
I spent sometime experimenting with various solutions to fix the missing second bug and I think I found a solution that works nicely. I didn't want it to be too complex, I'm not (currently) writing any systems essential for life. It just should make sure nothing is missing. Without it the observable will accumulate all published values and the first subscriber will recieve them all. We don't want this to happen because we are interested at what's now and now in the history.
So how does the Scheduler
work? I turned it into a hot observable with Publish
+ Connect
. I want it to be ticking regardles if there is anyone listening.
The new Scheduler
also can check how many jobs are already running and decline starting a new one.
How did I fix the missing second issue? The Scheduler
is publishing timestamps the same way it did before but now it additionally checks if the difference between the last and the current timestamp is greater then 1s
. If it is, it publishes the missing second before publishing the current one. This is achieved by switching from Select
to SelectMany
. Two intervals published virtually at the same moment don't really matter. More important is to guarantee that nothing is left out and that jobs at any second can be triggered. I think it's extremely unlikely that this would cause any issues ever.
public class Scheduler : IDisposable
public const int UnlimitedJobParallelism = -1;
private const int ZeroJobCounter = 0;
private IConnectableObservable<DateTime> _scheduler;
private IDisposable _disconnect;
private readonly ConcurrentDictionary<Job, int> _jobCounters;
public Scheduler(TimeSpan period, IDateTime dateTime)
var lastTimestamp = dateTime.Now();
_scheduler =
Observable
.Interval(period)
.SelectMany(_ =>
// If we missed one second due to time inaccuracy,
// this makes sure to publish the missing second too
// so that all jobs at that second can also be triggered.
var now = dateTime.Now();
var timestampDifference = now.Second - lastTimestamp.Second;
var timestamps =
Enumerable
// When starting the scheduler it might occur
// that the difference is a negative number (-59).
// If this is the case, make it 1.
.Range(1, timestampDifference < 0 ? 1 : timestampDifference)
.Select(second => lastTimestamp.AddSeconds(second));
lastTimestamp = now;
return timestamps.Dump();
)
// Share the actual data values and not just the observable instance.
.Publish();
// Turn this into a hot observable and start ticking
// regardless if there is anything listening.
_disconnect = _scheduler.Connect();
_jobCounters = new ConcurrentDictionary<Job, int>();
public Scheduler()
: this(TimeSpan.FromSeconds(1), new LocalDateTime())
public IDisposable Schedule(Job job)
var cronExpression = CronExpression.Parse(job.CronExpression);
var unschedule =
_scheduler
.Where(cronExpression.Contains)
.Subscribe(async timestamp =>
if (CanExecute(job))
_jobCounters.AddOrUpdate(job, 1, (j, c) => c + 1);
await job
.ExecuteAsync(timestamp)
.ContinueWith(_ =>
_jobCounters
.AddOrUpdate(job, 1, (j, c) => c - 1)
);
else
$"'job.Name' is alredy running!".Dump();
);
return Disposable.Create(() =>
_jobCounters.TryRemove(job, out var _);
unschedule.Dispose();
);
private bool CanExecute(Job job)
// Reversed the second condition to make the left side consistent.
return
job.MaxDegreeOfParallelism == UnlimitedJobParallelism
public void Dispose()
// Stop ticking.
_disconnect.Dispose();
There are also two new helpers. An extensions for the scheduler and the Job
class.
public static class SchedulerExtensions
public static IDisposable Schedule(this Scheduler scheduler, string cronExpression, Func<DateTime, Task> execute, int maxDegreeOfParallelism = 1)
return scheduler.Schedule(new Job
CronExpression = cronExpression,
ExecuteAsync = execute,
MaxDegreeOfParallelism = maxDegreeOfParallelism
);
public class Job
public string Name get; set;
public string CronExpression get; set;
public Func<DateTime, Task> ExecuteAsync get; set;
public int MaxDegreeOfParallelism get; set; = 1;
Example
The usage didn't change much, just the signatures.
void Main()
var scheduler = new Scheduler();
// Let the scheduler tick for a moment...
Thread.Sleep(3300);
scheduler.Schedule("0/1 * * * * * *", async schedule =>
Console.WriteLine($"DEBUG: schedule - schedule:ss.fff [Thread.CurrentThread.ManagedThreadId]");
await Task.Delay(1100);
, maxDegreeOfParallelism: Scheduler.UnlimitedJobParallelism);
scheduler.Schedule("0/2 * * * * * *", async schedule =>
Console.WriteLine($"ACTION-1: schedule - schedule:ss.fff");
await Task.Delay(1500);
, maxDegreeOfParallelism: Scheduler.UnlimitedJobParallelism);
scheduler.Schedule(new Job
Name = "Test-job",
CronExpression = "0/3 * * * * * *",
ExecuteAsync = async schedule =>
Console.WriteLine($"ACTION-2: schedule - schedule:ss.fff");
await Task.Delay(4000);
,
MaxDegreeOfParallelism = 1
);
c# timer async-await observer-pattern scheduled-tasks
It would probably be too easy if the old Scheduler
worked as intended being that simple. But an eye-openig review showed that from time to time it'll miss one second due to 14ms timer incaccuracy. This of course would have bad implications on the schedules (they wouldn't fire if their second was skipped).
I spent sometime experimenting with various solutions to fix the missing second bug and I think I found a solution that works nicely. I didn't want it to be too complex, I'm not (currently) writing any systems essential for life. It just should make sure nothing is missing. Without it the observable will accumulate all published values and the first subscriber will recieve them all. We don't want this to happen because we are interested at what's now and now in the history.
So how does the Scheduler
work? I turned it into a hot observable with Publish
+ Connect
. I want it to be ticking regardles if there is anyone listening.
The new Scheduler
also can check how many jobs are already running and decline starting a new one.
How did I fix the missing second issue? The Scheduler
is publishing timestamps the same way it did before but now it additionally checks if the difference between the last and the current timestamp is greater then 1s
. If it is, it publishes the missing second before publishing the current one. This is achieved by switching from Select
to SelectMany
. Two intervals published virtually at the same moment don't really matter. More important is to guarantee that nothing is left out and that jobs at any second can be triggered. I think it's extremely unlikely that this would cause any issues ever.
public class Scheduler : IDisposable
public const int UnlimitedJobParallelism = -1;
private const int ZeroJobCounter = 0;
private IConnectableObservable<DateTime> _scheduler;
private IDisposable _disconnect;
private readonly ConcurrentDictionary<Job, int> _jobCounters;
public Scheduler(TimeSpan period, IDateTime dateTime)
var lastTimestamp = dateTime.Now();
_scheduler =
Observable
.Interval(period)
.SelectMany(_ =>
// If we missed one second due to time inaccuracy,
// this makes sure to publish the missing second too
// so that all jobs at that second can also be triggered.
var now = dateTime.Now();
var timestampDifference = now.Second - lastTimestamp.Second;
var timestamps =
Enumerable
// When starting the scheduler it might occur
// that the difference is a negative number (-59).
// If this is the case, make it 1.
.Range(1, timestampDifference < 0 ? 1 : timestampDifference)
.Select(second => lastTimestamp.AddSeconds(second));
lastTimestamp = now;
return timestamps.Dump();
)
// Share the actual data values and not just the observable instance.
.Publish();
// Turn this into a hot observable and start ticking
// regardless if there is anything listening.
_disconnect = _scheduler.Connect();
_jobCounters = new ConcurrentDictionary<Job, int>();
public Scheduler()
: this(TimeSpan.FromSeconds(1), new LocalDateTime())
public IDisposable Schedule(Job job)
var cronExpression = CronExpression.Parse(job.CronExpression);
var unschedule =
_scheduler
.Where(cronExpression.Contains)
.Subscribe(async timestamp =>
if (CanExecute(job))
_jobCounters.AddOrUpdate(job, 1, (j, c) => c + 1);
await job
.ExecuteAsync(timestamp)
.ContinueWith(_ =>
_jobCounters
.AddOrUpdate(job, 1, (j, c) => c - 1)
);
else
$"'job.Name' is alredy running!".Dump();
);
return Disposable.Create(() =>
_jobCounters.TryRemove(job, out var _);
unschedule.Dispose();
);
private bool CanExecute(Job job)
// Reversed the second condition to make the left side consistent.
return
job.MaxDegreeOfParallelism == UnlimitedJobParallelism
public void Dispose()
// Stop ticking.
_disconnect.Dispose();
There are also two new helpers. An extensions for the scheduler and the Job
class.
public static class SchedulerExtensions
public static IDisposable Schedule(this Scheduler scheduler, string cronExpression, Func<DateTime, Task> execute, int maxDegreeOfParallelism = 1)
return scheduler.Schedule(new Job
CronExpression = cronExpression,
ExecuteAsync = execute,
MaxDegreeOfParallelism = maxDegreeOfParallelism
);
public class Job
public string Name get; set;
public string CronExpression get; set;
public Func<DateTime, Task> ExecuteAsync get; set;
public int MaxDegreeOfParallelism get; set; = 1;
Example
The usage didn't change much, just the signatures.
void Main()
var scheduler = new Scheduler();
// Let the scheduler tick for a moment...
Thread.Sleep(3300);
scheduler.Schedule("0/1 * * * * * *", async schedule =>
Console.WriteLine($"DEBUG: schedule - schedule:ss.fff [Thread.CurrentThread.ManagedThreadId]");
await Task.Delay(1100);
, maxDegreeOfParallelism: Scheduler.UnlimitedJobParallelism);
scheduler.Schedule("0/2 * * * * * *", async schedule =>
Console.WriteLine($"ACTION-1: schedule - schedule:ss.fff");
await Task.Delay(1500);
, maxDegreeOfParallelism: Scheduler.UnlimitedJobParallelism);
scheduler.Schedule(new Job
Name = "Test-job",
CronExpression = "0/3 * * * * * *",
ExecuteAsync = async schedule =>
Console.WriteLine($"ACTION-2: schedule - schedule:ss.fff");
await Task.Delay(4000);
,
MaxDegreeOfParallelism = 1
);
c# timer async-await observer-pattern scheduled-tasks
edited Apr 15 at 9:37
asked Apr 15 at 9:30
t3chb0t
32k54195
32k54195
I'd like to add that theJob
validation will be done later with my Simple object validator.
â t3chb0t
Apr 15 at 13:34
1
I have tried this, and it seems to work very well. One comment: because you're awaiting job.ExecuteAsync(...), you don't have to use Task.ContinueWith(...). In an attempt to dive into the Reactive namespace and in an attempt to "solve" the millisecond problem, I have elaborated on you work. Do you mind, if I post a question heavily cannibalising on you ideas?
â Henrik Hansen
Apr 16 at 7:34
@HenrikHansen not at all. Please do. I'd be happy to see your code and maybe learn something new from it and incorporate it into my solution ;-)
â t3chb0t
Apr 16 at 7:37
add a comment |Â
I'd like to add that theJob
validation will be done later with my Simple object validator.
â t3chb0t
Apr 15 at 13:34
1
I have tried this, and it seems to work very well. One comment: because you're awaiting job.ExecuteAsync(...), you don't have to use Task.ContinueWith(...). In an attempt to dive into the Reactive namespace and in an attempt to "solve" the millisecond problem, I have elaborated on you work. Do you mind, if I post a question heavily cannibalising on you ideas?
â Henrik Hansen
Apr 16 at 7:34
@HenrikHansen not at all. Please do. I'd be happy to see your code and maybe learn something new from it and incorporate it into my solution ;-)
â t3chb0t
Apr 16 at 7:37
I'd like to add that the
Job
validation will be done later with my Simple object validator.â t3chb0t
Apr 15 at 13:34
I'd like to add that the
Job
validation will be done later with my Simple object validator.â t3chb0t
Apr 15 at 13:34
1
1
I have tried this, and it seems to work very well. One comment: because you're awaiting job.ExecuteAsync(...), you don't have to use Task.ContinueWith(...). In an attempt to dive into the Reactive namespace and in an attempt to "solve" the millisecond problem, I have elaborated on you work. Do you mind, if I post a question heavily cannibalising on you ideas?
â Henrik Hansen
Apr 16 at 7:34
I have tried this, and it seems to work very well. One comment: because you're awaiting job.ExecuteAsync(...), you don't have to use Task.ContinueWith(...). In an attempt to dive into the Reactive namespace and in an attempt to "solve" the millisecond problem, I have elaborated on you work. Do you mind, if I post a question heavily cannibalising on you ideas?
â Henrik Hansen
Apr 16 at 7:34
@HenrikHansen not at all. Please do. I'd be happy to see your code and maybe learn something new from it and incorporate it into my solution ;-)
â t3chb0t
Apr 16 at 7:37
@HenrikHansen not at all. Please do. I'd be happy to see your code and maybe learn something new from it and incorporate it into my solution ;-)
â t3chb0t
Apr 16 at 7:37
add a comment |Â
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f192103%2fscheduler-built-with-observables-v2-follow-up%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
I'd like to add that the
Job
validation will be done later with my Simple object validator.â t3chb0t
Apr 15 at 13:34
1
I have tried this, and it seems to work very well. One comment: because you're awaiting job.ExecuteAsync(...), you don't have to use Task.ContinueWith(...). In an attempt to dive into the Reactive namespace and in an attempt to "solve" the millisecond problem, I have elaborated on you work. Do you mind, if I post a question heavily cannibalising on you ideas?
â Henrik Hansen
Apr 16 at 7:34
@HenrikHansen not at all. Please do. I'd be happy to see your code and maybe learn something new from it and incorporate it into my solution ;-)
â t3chb0t
Apr 16 at 7:37