David C
David C

Reputation: 2796

Multiple timer/callbacks — best approach to prevent duplicates and to monitor them

I have a c# console, that I have made into a Windows service, which I would like to run reliably and constantly.

  1. I want to prevent overlap of the same timer firing again
  2. I want to prevent different timers trying to use the same resource at once
  3. I want to be able to monitor the timers and interact with then.

It has a few aspects to it. Each runs very regularly. I have previously read about TaskScheduler vs Windows Service running this kind of thing, and have opted for this approach because something is running almost constantly.

I'm using timer callbacks, each with their own, similar to this simplified version:

class Program
{
    static PollingService _service;

    static void Main()
    {
        _service = new PollingService();

        TimerCallback tc1 = _service.TaskType1;
        TimerCallback tc2 = _service.TaskType2;
        TimerCallback tc3 = _service.TaskType3A;
        TimerCallback tc4 = _service.TaskType3B;

        Timer t1 = new Timer(tc1, null, 1000, 5000);
        Timer t2 = new Timer(tc2, null, 2000, 8000);
        Timer t3 = new Timer(tc3, null, 3000, 11000);
        Timer t4 = new Timer(tc4, null, 4000, 13000);


        Console.WriteLine("Press Q to quit");
        while (Console.ReadKey(true).KeyChar != 'q')
        {
        }
    }
}

class PollingService
{
    public void TaskType1(object state)
    {
        for (int i = 1; i <= 10; i++)
        {
            Console.WriteLine($"TaskOne numbering {i}");
            Thread.Sleep(100);
        }
    }

    public void TaskType2(object state)
    {
        for (int i = 10; i <= 100; i++)
        {
            Console.WriteLine($"TaskTwo numbering {i}");
            Thread.Sleep(100);
        }
    }

    public void TaskType3A(object state)
    {
        Increment(200000000);
    }

    public void TaskType3B(object state)
    {
        Increment(40000);
    }

    private void Increment(int startNumber)
    {
        for (int i = startNumber; i <= startNumber + 1000; i++)
        {
            Console.WriteLine($"Private {startNumber} numbering {i}");
            Thread.Sleep(5);
        }
    }
}

1 Firstly I want to ensure these don't get tied up with each other when one sometimes runs long.
Eg. If Task one takes 20 seconds to run sometimes, I want to prevent a duplicate timer while the previous might still be running, the same for all of the timers infact. Eg. if t2 is running for a little longer than usual then don't start another. I've read a little about if (Monitor.TryEnter(lockObject)), is that the best way to handle that requirement?

2 Secondly if they both access the same resource (in my case an EF context), such that t3 is already using it, and t4 tries to do so. Is there a way of asking the timer to wait until the other finishes?

3 Lastly is there a way I can monitor these timer/callbacks? I'd like to provide an UI to see the state of this when I have it running as a windows service. My endgame there is to provide a UI that users can see if a task is running, and if not then trigger it on demand if one isn't set to run for a little while. But in the same breath, not create a duplicate while one is running.

I have wondered whether I should've asked these as separate questions, but they seem so entwined with the decision of each other.

Upvotes: 2

Views: 2652

Answers (3)

Brandon
Brandon

Reputation: 4593

If you have to make sure that each thread doesn't have any overlap, you can use the Timer.Change(int, int) method to stop executing at the start of the callback, and then resume it at the end of the callback. You can also do some magic with a ManualResetEvent for each thread but it'll get messy.

I'm not a fan of timers for threading and try to avoid them whenever I can. If you can sacrifice the "each thread must run after n seconds", do it. Use tasks with a cancellation token instead, it will solve your overlap problem. For example:

A.

public class Foo
{
    private CancellationTokenSource _cts;
    //In case you care about what tasks you have.
    private List< Task > _tasks;

    public Foo()
    {
        this._cts = new CancellationTokenSource();

        this._tasks.Add(Task.Factory.StartNew(this.Method1, this._cts.Token));
        this._tasks.Add(Task.Factory.StartNew(this.Method2, this._cts.Token));
        this._tasks.Add(Task.Factory.StartNew(this.Method3, this._cts.Token));
        this._tasks.Add(Task.Factory.StartNew(this.Method4, this._cts.Token));


    }

    private void Method1(object state)
    {
        var token = (CancellationToken) state;
        while ( !token.IsCancellationRequested )
        {
            //do stuff
        }

    }
    private void Method2(object state)
    {
        var token = (CancellationToken)state;
        while (!token.IsCancellationRequested)
        {
            //do stuff
        }
    }
    private void Method3(object state)
    {
        var token = (CancellationToken)state;
        while (!token.IsCancellationRequested)
        {
            //do stuff
        }
    }
    private void Method4(object state)
    {
        var token = (CancellationToken)state;
        while (!token.IsCancellationRequested)
        {
            //do stuff
        }
    }

    public void StopExecution()
    {
        this._cts.Cancel();
    }
}

An EF context will throw an exception if used by more than one thread at a time. There is a way to synchronize it, using lock. It would look something like this, given the example above:

B.

public class Foo
{
    private object _efLock;
    public Foo()
    {
        this._efLock = new object();
    }
.
.
.
    private void MethodX(object state)
    {
        var token = (CancellationToken)state;
        while (!token.IsCancellationRequested)
        {
            lock(this._efLock)
            {
                 using(.......
            }
        }
    }
}

You'll have to do that in each thread that accesses your EF context. Keep in mind that, again, maintenance gets annoying because of the cognitive load that goes with complex lock scenarios.

I recently developed an application in which I needed multiple threads to access the same EF context. As I mentioned above, the locking got to be too much (and there was a performance requirement), so I devised a solution where each thread adds its object to a common queue, and a separate thread does nothing but pull data from the queue and call into EF. That way the EF context is only ever accessed by one thread. Problem solved. Here is what that would look like given the sample above:

C.

public class Foo
{
    private struct InternalEFData
    {
        public int SomeProperty;
    }


    private CancellationTokenSource _dataCreatorCts;
    private CancellationTokenSource _efCts;

    //In case you care about what tasks you have.
    private List< Task > _tasks;
    private Task _entityFrameworkTask;

    private ConcurrentBag< InternalEFData > _efData;


    public Foo()
    {
        this._efData = new ConcurrentBag< InternalEFData >();

        this._dataCreatorCts = new CancellationTokenSource();
        this._efCts = new CancellationTokenSource();

        this._entityFrameworkTask = Task.Factory.StartNew(this.ProcessEFData, this._efCts.Token);

        this._tasks.Add(Task.Factory.StartNew(this.Method1, this._dataCreatorCts.Token));
        this._tasks.Add(Task.Factory.StartNew(this.Method2, this._dataCreatorCts.Token));
        .
        .
        .

    }

    private void ProcessEFData(object state)
    {
        var token = (CancellationToken) state;
        while ( !token.IsCancellationRequested )
        {
            InternalEFData item;
            if (this._efData.TryTake(out item))
            {
                using ( var efContext = new MyDbContext() )
                {
                    //Do processing.    
                }
            }
        }

    }

    private void Method1(object state)
    {
        var token = (CancellationToken) state;
        while ( !token.IsCancellationRequested )
        {
            //Get data from whatever source
            this._efData.Add(new InternalEFData());
        }

    }

    private void Method2(object state)
    {
        var token = (CancellationToken) state;
        while ( !token.IsCancellationRequested )
        {
            //Get data from whatever source
            this._efData.Add(new InternalEFData());
        }
    }


    public void StopExecution()
    {
        this._dataCreatorCts.Cancel();
        this._efCts.Cancel();
    }
}

When it comes to reading data from executing threads, I generally use a SynchronizationContext. I don't know if it's the right object to use and someone else can probably comment on that. Create a Synchronization object, pass it to your threads and have them update it with the necessary data and post it to your UI/Console thread:

D.

public struct SyncObject
{
    public int SomeField;
}

public delegate void SyncHandler(SyncObject s);

public class Synchronizer
{
    public event SyncHandler OnSynchronization;

    private SynchronizationContext _context;

    public Synchronizer()
    {
        this._context = new SynchronizationContext();
    }

    public void PostUpdate(SyncObject o)
    {
        var handleNullRefs = this.OnSynchronization;
        if ( handleNullRefs != null )
        {
            this._context.Post(state => handleNullRefs((SyncObject)state), o);
        }
    }
}

public class Foo
{
    private Synchronizer _sync;
    public Foo(Synchronizer s)
    {
        this._sync = s;
    }
    private void Method1(object state)
    {
        var token = (CancellationToken) state;
        while ( !token.IsCancellationRequested )
        {
            //do things
            this._sync.PostUpdate(new SyncObject());
        }

    }
}

Again, that's how I do it, I don't know if it's the proper way.

Upvotes: 2

TomTom
TomTom

Reputation: 62101

1: Likely the best is not to do anything in the timers but stat a task - IF any WHEN a flag is set or not set. Look for interlocked (the class) on how to implement that without locking.

2: Monitor. But seriously, why do they share an EF contect?

3: Sure. Create performance counters. Monitor them. The API is in windows for many many years.

Upvotes: 0

Haukinger
Haukinger

Reputation: 10873

  1. basically, yes, or AutoResetEvent
  2. you can stop it, wait for the resource to free up, and then restart it
  3. keep a list of states associated with your timers, and update those states from the timers (set to running when starting, set to waiting when done, or something along these lines)

Upvotes: 0

Related Questions