4
\$\begingroup\$

Scenario:

I want a task scheduler that enables my application to schedule some tasks in small time interval. I want to use it in my socket server application. For example, when the user connects to the socket application schedule some task in 15 seconds to disconnect user if no actions were made.

  • Queuing task for specific time
  • Ability to stop scheduled jobs
  • Running jobs in separate thread

So, I've decided to create my own Task Schedule. It can schedule tasks. It use System.Timers.Timer to find tasks to run. I want you to review my decision, may be solution has architecture errors etc.

public abstract class ScheduleJob
{
    //Date when task must be executed
    public DateTime DateTrigger { get; set; }

    //Task Id (for search and cancel)
    public string TaskId { get; set; }

    //For logs
    public abstract string TaskName { get; }

    //Main task method
    protected abstract void OnStart();

    //Default task class delay (for example 15 seconds)
    public abstract TimeSpan ScheduleTo { get; }

    private bool _isRunning = false;


    public void Start()
    {
        _isRunning = true;

        var s = new Stopwatch();
        s.Start();

        try
        {
            //run task in separated thread
            Task.Factory.StartNew(OnStart).ContinueWith(t =>
            {
                s.Stop();
                _isRunning = false;

                //if (t.Exception != null)
                //{
                //TODO work with exceptions
                //}
            });
        }
        catch (Exception ex)
        {
            //TODO work with exceptions
        }
    }
}

Manager to work with jobs

public sealed class ScheduleManager : IDisposable
{
    private static ScheduleManager _instance = new ScheduleManager();

    public static ScheduleManager Instance { get { return _instance; } }


    private List<ScheduleJob> _jobs;
    private object _lock = new object();

    private Timer _timer;

    private ScheduleManager()
    {
        _jobs = new List<ScheduleJob>();
        _timer = new Timer();
        _timer.Interval = 1000;
        _timer.Elapsed += TimerOnElapsed;
        _timer.Start();
    }

    private void TimerOnElapsed(object sender, ElapsedEventArgs elapsedEventArgs)
    {
        lock (_lock)
        {
            var tasks = _jobs.Where(x => x.DateTrigger <= DateTime.Now).ToList();

            foreach (ScheduleJob scheduleJob in tasks)
            {
                scheduleJob.Start();
                _jobs.Remove(scheduleJob);
            }
        }
    }

    public void Schedule(ScheduleJob job)
    {
        lock (_lock)
        {
            job.DateTrigger = DateTime.Now + job.ScheduleTo;
            _jobs.Add(job);
        }
    }

    public void Remove(string taskId)
    {
        lock (_lock)
        {
            _jobs.RemoveAll(x => x.TaskId.Equals(taskId, StringComparison.OrdinalIgnoreCase));
        }
    }

    public void Dispose()
    {
        if (_timer != null)
        {
            _timer.Stop();
            //todo stop or cancel jobs
            _timer = null;
        }
    }
}

Example of job UserShouldSendAuthTokenAfterConnectToSocketTask

public class UserShouldSendAuthTokenAfterConnectToSocketTask : ScheduleJob
{
    private string _connectionId;


    public UserShouldSendAuthTokenAfterConnectToSocketTask(string connectionId)
    {
        _connectionId = connectionId;
        TaskId = GenerateTaskId(_connectionId);
    }

    public static string GenerateTaskId(string connectionId)
    {
        return $"UserShouldSendAuthTokenAfterConnectToSocketTask_{connectionId}";
    }

    public override string TaskName { get { return "User should auth to socket"; } }

    protected override void OnStart()
    {
        SocketServer.Instance.SendMessage(new UserShouldSendAuthTokenAfterConnectToSocket(), _connectionId);
    }

    public override TimeSpan ScheduleTo
    {
        get
        {
            return TimeSpan.FromSeconds(GameConfig.SecondsUserShouldSendAuthTokenAfterConnectToSocket);
        }
    }
}
\$\endgroup\$
2
\$\begingroup\$

Good work. A few comments and questions.

  1. In SchduleJob class, I think that DateTrigger and TaskId need to be defined as internal and the SchduleManager only need to set them. If they will be public, the concrete class may set them to some value and the manager will override it in the Schedule method.
  2. I think you need to constraint the TaskId to be unique and the manager only set it with a unique validation. For user define name\id you the TaskName property.
  3. According to section 2, in the Remove method, replace RemoveAll by Remove.
  4. Consider to change the type to long\int instead string, I don't talk about performance but about meaning although string is also make sense.
  5. If you prefer to use string for the id, use string.CompareOrdinal() for the comprasion.
  6. I think all the logging (add, remove, start, stop, error) need to be in the manager. The ScheduleJob need to be dedicated to the job. It can handle exception if you want, but the manager need to log it also.
  7. Make the ScheduleManager IDisposable is dangerhere. Its a singleton and no body going to use it with using statement, but if somebody will do it, it make a problem because your timer will be disposable...
\$\endgroup\$
  • \$\begingroup\$ Thanks a lot for comments! I have question about item 4. I made TaskId string, because of: a) may be different types of tasks with user object. b) String, so manager can delete all types of start using StartWith... \$\endgroup\$ – Dmitriy Dec 24 '15 at 11:57
  • \$\begingroup\$ @Dmitriy A) For this you can use the TaskName or add TaskType property. Then if you want filter by type, you can to it. B) Again, I recommend to add TaskType for this. For the regular Remove method use the id, its cheaper from StartWith. Also the type doesn't need to be unique but the ID in my opinion, need to be unique. Anyway, its just a recommendation.. \$\endgroup\$ – Dudi Keleti Dec 24 '15 at 12:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.