Вопрос

I'm using a System.Timers.Timer in my application. Every second I run a function which does some job. The thing is, this function can block for some little time (it reads then processes a large file from disk). I want to start that function only if its previous "execution instance" has completed. I thought I could achieve this with a Mutex:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
        static Mutex TimerMut = new Mutex(false);
        public static void Main()
        {

            Thread TT = new Thread(new ThreadStart(delegate()
            {
                System.Timers.Timer oTimer = new System.Timers.Timer();
                oTimer.Elapsed += new ElapsedEventHandler(Handler);

                oTimer.Interval = 1000;
                oTimer.Enabled = true;
            }));

            TT.Start();

            Console.Read();
        }

        private static void Handler(object oSource,
            ElapsedEventArgs oElapsedEventArgs)
        {
            TimerMut.WaitOne();
            Console.WriteLine("foo");
            Thread.Sleep(500);         //simulate some work
            Console.WriteLine("bar");
            TimerMut.ReleaseMutex();
        }
    }
}

That doesn't work, "foos" still appear every second. How can I achieve this?

EDIT: You're right, it makes no sense to start a new thread to handle this. I thought only System.Threading.Timer is launched in a separate thread.

Это было полезно?

Решение 3

Easiest way I know of to do this kind of thing:

internal static volatile bool isRunning;

public static void Main()
    {
        Thread TT = new Thread(new ThreadStart(delegate()
            {
                System.Timers.Timer oTimer = new System.Timers.Timer();
                oTimer.Elapsed += new ElapsedEventHandler(Handler);

                oTimer.Interval = 1000;
                oTimer.Enabled = true;                 
            }));

        TT.Start();
    }

    private void Handler(object oSource,
        ElapsedEventArgs oElapsedEventArgs)
    {
        if(isRunning) return;

        isRunning = true;

        try
        {
           Console.WriteLine("foo");
           Thread.Sleep(500);         //simulate some work
           Console.WriteLine("bar");
        }
        finally { isRunning = false; }            
    }

The handler still runs, but the very first thing it does is make sure that another handler isn't running, and if one is, it stops immediately.

For timers executing handlers more quickly (like 3-4 times a second), this has the possibility to race; two threads could proceed past the guard clause before one of them sets the bit. You can avoid this with a couple of lock statements, similar to a Mutex or Monitor:

    static object syncObj = new object();

    private void Handler(object oSource,
        ElapsedEventArgs oElapsedEventArgs)
    {
        lock(syncObj)
        {
           if(isRunning) return;            
           isRunning = true;
        }

        try
        {
           Console.WriteLine("foo");
           Thread.Sleep(500);         //simulate some work
           Console.WriteLine("bar");
        }
        finally { lock(syncObj) { isRunning = false; } }            
    }

This will ensure that only one thread can ever be examining or modifying isRunning, and as isRunning is marked volatile, the CLR won't cache its value as part of each thread's state for performance; each thread has to look at exactly the same memory location to examine or change the value.

Другие советы

I'm not sure why you are using a new thread to start the timer, since timers run on their own thread, but here's a method that works. Simply turn the timer off until you are done with the current interval.

static System.Timers.Timer oTimer

public static void Main()
{
    oTimer = new System.Timers.Timer();
    oTimer.Elapsed += new ElapsedEventHandler(Handler);

    oTimer.Interval = 1000;
    oTimer.Enabled = true;                 
}

private void Handler(object oSource, ElapsedEventArgs oElapsedEventArgs)
{
    oTimer.Enabled = false;

    Console.WriteLine("foo");
    Thread.Sleep(5000);         //simulate some work
    Console.WriteLine("bar");

    oTimer.Enabled = true;
}

If you want to skip the tick if another is already working you can do this.

private readonly object padlock = new object();

private void SomeMethod()
{
    if(!Monitor.TryEnter(padlock))
        return;

    try
    {
        //Do heavy work
    }
    finally
    {
        Monitor.Exit(padlock);
    }
}

You can follow the following pattern to skip doing the indicated work if another invocation of this method is still running:

private int isWorking = 0;
public void Foo()
{
    if (Interlocked.Exchange(ref isWorking, 1) == 0)
    {
        try
        {
            //Do work
        }
        finally
        {
            Interlocked.Exchange(ref isWorking, 0);
        }
    }
}

The approach that you were using with a Mutex will result in addition ticks waiting for earlier ticks to finish, not skipping invocations when another is still running, which is what you said you wanted. (When dealing with timers like this its common to want to skip such ticks, not wait. If your tick handlers regularly take too long you end up with a giant queue of waiting handlers.)

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top