Mike Marks
Mike Marks

Reputation: 10139

How to create a nested System.Timer in C#

What's the general practice for creating a nested System.Timer? Basically, I need a parent timer that will run indefinitely every 10 seconds, and a child timer that will run a determined length of time every second. The thing I'm having a problem with is how to properly exit the child timer once it's finished.

Here's a simple example - I have a parent timer with a corresponding ElapsedEventHandler. Within this event I make a call to a third party API that returns a list of objects. Now that I have these objects, I need to make extra API calls on each of these objects, one call every second (enter the child timer). Say the first API call brings back 30 objects. I want the ElapsedEventHandler for the child timer to only run 30 times, then exit. I'm just not sure how to implement this.

The reason for my timers is because this third party API imposes a speed limit.

Upvotes: 1

Views: 997

Answers (3)

Paul Reedy
Paul Reedy

Reputation: 480

Do you need to fetch every 10 seconds, or when all of the data has been processed? You mention fetching data every 10 seconds, but getting 30 items back. At that rate, you'll keep allocating memory as it'll never be able to catch up.

1 second timer then increment a static counter to fetch data every 10 iterations. Once data is retrieved, each item is popped off the stack every second.

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

namespace TimedFetch
{
    class Program
    {
        static System.Threading.Timer workTimer;
        static Stack<string> itemList;
        public static AutoResetEvent fetchDataEvent;
        static int eventCounter;

        public class ExternalDatamanager
        {
            public void FetchData()
            {
                DateTime startTime = DateTime.Now;

                do
                {
                    FetchHeaderData();
                    fetchDataEvent.WaitOne();
                    Console.WriteLine("{0} FetchData Signaled! List size {1}", DateTime.Now.ToLongTimeString(), itemList.Count);
                } while(true);
            }

            private static void FetchHeaderData()
            {
                // replace this with your method of retrieving data
                Console.WriteLine("{0} FetchHeaderData", DateTime.Now.ToLongTimeString());
                DateTime timeObject = DateTime.Now;
                for (int i = 0; i < 30; i++)
                {
                    lock (itemList)
                    {
                        itemList.Push(timeObject.ToLongTimeString());
                        timeObject = timeObject.AddSeconds(1);
                    }
               }
            }
        }

        static void Main(string[] args)
        {
            eventCounter = 0;
            itemList = new Stack<string>();
            fetchDataEvent = new AutoResetEvent(false);

            ExternalDatamanager dataManager = new ExternalDatamanager();
            Thread workerThread = new Thread(new ThreadStart(dataManager.FetchData));
            workerThread.Start();

            workTimer = new System.Threading.Timer(workTimer_Elapsed, null, 0, 1000);
        }

        // executes once a second
        private static void workTimer_Elapsed(Object state)
        {
            Console.WriteLine("{0} Fire!", DateTime.Now.ToLongTimeString());
            Interlocked.Increment(ref eventCounter);

            lock (itemList)
            {
                // every 10 seconds
                if (itemList.Count > 0)
                    FetchDetail(itemList.Pop());
                // else ??
                // { ??
                if (eventCounter % 10 == 0)
                    fetchDataEvent.Set();
                // } ??
            }
        }

        private static void FetchDetail(string headerRecord)
        {
            Console.WriteLine("{0} Fetch detail for {1}", DateTime.Now.ToLongTimeString(), headerRecord);
        }
    }
}

Upvotes: 1

DavidN
DavidN

Reputation: 5197

Sounds like you just need a global processing Queue with a single timer set up for whatever throttle interval the API vendor imposes upon you. All API requests would be queued up, and the results of any API requests that would require further child requests are queued up as well. You have a single timer which processes the next item on the Queue every second.

Here's a sample of what I'm talking about. I'd create a sub-class for each API request, so that it will a) correctly process the request and b) enqueue sub-tasks as needed, setting state of child tasks from the results of the current API request if needed:

void Main()
{
    Timer t = new Timer(ProcessNextItem, null, 0, 1000);
}

public void ProcessNextItem(object o){
    var nextTask = Tasks.Take();

    nextTask.Process(); // Process does the API calls and enqueues new tasks onto Tasks, and sets state of child tasks as needed
}

public static BlockingCollection<APIRequestTask> Tasks = new BlockingCollection<APIRequestTask>(new ConcurrentQueue<APIRequestTask>());

public abstract class APIRequestTask{
    public void Process(){}
    public object State {get;set;}
}

Upvotes: 1

Khan
Khan

Reputation: 18162

If the API caller is running on a separate thread, forget using a child timer. Just make the thread sleep for a second between pulls.

Something like this:

foreach(var item in apiObjectsToGet)
{
    api.GetItem(item);
    Thread.Sleep(1000);
}

Upvotes: 0

Related Questions