简体   繁体   中英

Pass System.Threading.Timer object reference to its callback function

is it possible to pass the System.Threading.Timer object reference to its callback function, something like this:

System.Threading.Timer myTimer = new System.Threading.Timer(new TimerCallback(DoSomething), myTimer, 2000, Timeout.Infinite);

Because in "DoSomething" method I want to call:

myTimer.Change(5000, Timeout.Infinite);

I'll paste a draft console application below. Idea is this: I have List of timers. And every timer makes some request and when it receives it, it changes some shared data. But, I can't pass the reference to timer into its callback, nor can I use it's index, because it becomes "-1" for some reason(investigating)..

using System;
using System.Collections.Generic;
using System.Threading;

namespace TimersInThreads
{
    class Program
    {
        public static int sharedDataInt;
        static private readonly object lockObject = new object();
        public static List<System.Threading.Timer> timers = new List<Timer>();

        static void Main(string[] args)
        {
            System.Threading.Timer timer = new System.Threading.Timer(new TimerCallback(DoSomething), timers.Count - 1, 2000, Timeout.Infinite);
            timers.Add(timer);

            System.Threading.Timer timer2 = new System.Threading.Timer(new TimerCallback(DoSomething), timers.Count - 1, 2000, Timeout.Infinite);
            timers.Add(timer2);

            System.Threading.Timer timer3 = new System.Threading.Timer(new TimerCallback(DoSomething), timers.Count - 1, 2000, Timeout.Infinite);
            timers.Add(timer3);

            //timer = new System.Threading.Timer(new TimerCallback(DoSomething), "Timer 1", 1000, Timeout.Infinite);
            //timer = new System.Threading.Timer(new TimerCallback(DoSomething), "Timer 2", 450, Timeout.Infinite);
            //timer = new System.Threading.Timer(new TimerCallback(DoSomething), "Timer 3", 1500, Timeout.Infinite);

            Console.ReadLine();

        }

        static void DoSomething(object timerIndex)
        {
            // Request
            // Get Response
            var x = getSomeNumberWithDelay();


            // Executes after Response is received
            lock (lockObject)
            {
                sharedDataInt++;
                Console.WriteLine("Timer" + (int)timerIndex + ", SHaredDataInt: " + sharedDataInt + "\t\t" + DateTime.Now.ToString("HH:mm:ss tt") + "." + DateTime.Now.Millisecond.ToString());
            }

            timers[(int)timerIndex].Change(5000, Timeout.Infinite);
        }

        static int getSomeNumberWithDelay()
        {
            Thread.Sleep(5000);
            return 3;
        }
    }
}

Please, give me some idea or advice. Much appreciated, thanks!

The state parameter in the Timer constructor is passed as an argument to the TimerCallback - this is just an object therefore will work with anything, including the timer reference itself.

So

new System.Threading.Timer(new TimerCallback(DoSomething), myTimer, 2000, Timeout.Infinite);

is perfectly acceptable. In your callback you would just need to cast the parameter as Timer eg

static void DoSomething(object state)
{
    ...
    var timer = (System.Threading.Timer)state;
}

Looking at your problem again, I see what you are trying to do is pass the timer as a parameter into it's constructor (which obviously can't be done as it hasn't been officially declared yet). You can workaround this though by passing the timer to the callback explicitly eg

Timer t = null;
t = new Timer(delegate { DoSomething(t); }, null, 2000, Timeout.Infinite);

By the time the callback is triggered t will set to the reference of the timer.

Replace each occurrence of timers.Count - 1 with timers.Count :

System.Threading.Timer timer = new System.Threading.Timer(new TimerCallback(DoSomething), timers.Count, 2000, Timeout.Infinite);
timers.Add(timer);

When you adding first timer to the list, there is no elements there, so the timers.Count equals to 0 .

Alternative way is to pass an instance of particular timer as the second argument of the callback instead of its index.

I have a class for this purpose. It provides a TypedCallback, with State, and a reference to the Timer. The class maintains a List of references and provides Disposal.

I don't have enough "points" to make comments, but also wanted to point out that in James' answer, you need to be sure to retain a reference to the Timer. As the documentation states, they can become garbage even if still running.

/// <summary>
/// Holds a list of references to Timers; and provides typed objects to reference the Timer and its
/// State from within a TypedCallback. Synchronized. Usage:
/// <code>
/// TypedStateTimers myTimers = new TypedStateTimers(3);
/// public void MyMethod() {
///     typedStateTimers.Create("Hello, from Timer", new TypedCallback<string>((sr) => {
///         System.Console.WriteLine(sr.State); // "Hello, from Timer"
///         sr.Dispose(); // Invoke the StateRef method to ensure references are released
///     })).Start(1500, Timeout.Infinite);
/// }
/// </code>
/// </summary>
public class TypedStateTimers
{
    /// <summary>
    /// A typed delegate used as the callback when Timers are created.
    /// </summary>
    public delegate void TypedCallback<T>(StateRef<T> state);


    /// <summary>
    /// Wraps a Timer and State object to be used from within a TypedCallback.
    /// </summary>
    public class StateRef<T> : IDisposable
    {
        /// <summary>The state passed into TypedStateTimers.Create. May be null.</summary>
        public T State { get; internal set; }

        /// <summary>The Timer: initially not started. Not null.</summary>
        public System.Threading.Timer Timer { get; internal set; }

        /// <summary>The TypedStateTimers instance that created this object. Not null.</summary>
        public TypedStateTimers Parent { get; internal set; }


        /// <summary>
        /// A reference to this object is retained; and then Timer's dueTime and period are changed
        /// to the arguments.
        /// </summary>
        public void Start(int dueTime, int period) {
            lock (Parent.Treelock) {
                Parent.Add(this);
                Timer.Change(dueTime, period);
            }
        }

        /// <summary>Disposes the Timer; and releases references to Timer, State, and this object.</summary>
        public void Dispose() {
            lock (Parent.Treelock) {
                if (Timer == null) return;
                Timer.Dispose();
                Timer = null;
                State = default(T);
                Parent.Remove(this);
            }
        }
    }


    internal readonly object Treelock = new object();
    private readonly List<IDisposable> stateRefs;


    /// <summary>
    /// Constructs an instance with an internal List of StateRef references set to the initialCapacity.
    /// </summary>
    public TypedStateTimers(int initialCapacity) {
        stateRefs = new List<IDisposable>(initialCapacity);
    }


    /// <summary>Invoked by the StateRef to add it to our List. Not Synchronized.</summary>
    internal void Add<T>(StateRef<T> stateRef) {
        stateRefs.Add(stateRef);
    }

    /// <summary>Invoked by the StateRef to remove it from our List. Not synchronized.</summary>
    internal void Remove<T>(StateRef<T> stateRef) {
        stateRefs.Remove(stateRef);
    }

    /// <summary>
    /// Creates a new StateRef object containing state and a new Timer that will use the callback and state.
    /// The Timer will initially not be started. The returned object will be passed into the callback as its
    /// argument. Start the Timer by invoking StateRef.Start. Dispose the Timer, and release references to it,
    /// state, and the StateRef by invoking StateRef.Dispose. No references are held on the Timer, state or
    /// the StateRef until StateRef.Start is invoked. See the class documentation for a usage example.
    /// </summary>
    public StateRef<T> Create<T>(T state, TypedCallback<T> callback) {
        StateRef<T> stateRef = new StateRef<T>();
        stateRef.Parent = this;
        stateRef.State = state;
        stateRef.Timer = new System.Threading.Timer(
                new TimerCallback((s) => { callback.Invoke((StateRef<T>) s); }),
                stateRef, Timeout.Infinite, Timeout.Infinite);
        return stateRef;
    }

    /// <summary>Disposes all current StateRef instances; and releases all references.</summary>
    public void DisposeAll() {
        lock (Treelock) {
            IDisposable[] refs = stateRefs.ToArray();
            foreach (IDisposable stateRef in refs) stateRef.Dispose();
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM