Stop Internal Timer in C# component

Hi everyone,

I am developing a C# component that includes an internal timer, which I use to automatically save files at regular intervals. However, I’m facing an issue: the timer keeps running regardless of the component’s status. For example, when the component is locked, I want the timer to stop, and when it’s unlocked, I want the timer to resume.

I’ve tried to implement this behavior but it’s beyond my current knowledge. Does anyone have experience or insights on how to control the timer based on the component’s state? Below is some of the logic I have so far:


// --------------------------------------------------------------------------
//  Main Autosave Component Logic
// ---------------------------------------------------------------------------

public class AutoSaveComponent : GH_Component{public int IntervalMs { get; private set; } = 900000;public int MaxFiles { get; private set; } = 5;
private Timer saveTimer;
public string AutosavePath { get; private set; } = @"C:\Temp\gh_autosave";
public string LastAutoSaveTime { get; private set; } = "---";

private string unsavedFolderName = null;

public AutoSaveComponent() : base("AutoSave", "AutoSave", "Automatically saves Grasshopper documents at a set interval.", "APP", "Template")
{
    SetupTimer();
}

// --- Initialize the autosave timer ---
private void SetupTimer()
{
    saveTimer = new Timer();
    saveTimer.Interval = IntervalMs;
    saveTimer.Tick += (s, e) => PerformAutoSave();
    saveTimer.Start();
}

// --- Update autosave interval ---
public void UpdateInterval(int newIntervalMinutes)
{
    IntervalMs = newIntervalMinutes * 60000;
    saveTimer.Interval = IntervalMs;
}

// --- Update maximum number of autosave files ---
public void UpdateMaxFiles(int newMax)
{
    if (newMax > 0) MaxFiles = newMax;
}

// --- Perform the autosave ---
private void PerformAutoSave()
{
    var doc = OnPingDocument();
    if (doc == null) return;
    try
    {
        if (!Directory.Exists(AutosavePath)) Directory.CreateDirectory(AutosavePath);
        string folderName = ResolveFolderName(doc);
        string saveFolder = Path.Combine(AutosavePath, folderName);

        if (!Directory.Exists(saveFolder)) Directory.CreateDirectory(saveFolder);
        string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
        string fileName = $"{folderName}_{timestamp}.gh";
        string fullPath = Path.Combine(saveFolder, fileName);

        GH_DocumentIO io = new GH_DocumentIO(doc);
        io.SaveQuiet(fullPath);

        // --- Remove old files exceeding MaxFiles ---
        var files = new DirectoryInfo(saveFolder).GetFiles("*.gh").OrderBy(f => f.CreationTime).ToList();

        while (files.Count > MaxFiles)
        {
            files.First().Delete();
            files.RemoveAt(0);
        }
        LastAutoSaveTime = DateTime.Now.ToLongTimeString();
        ExpireSolution(true);
    }
    catch (Exception ex)
    {
        Rhino.RhinoApp.WriteLine("AutoSave error: " + ex.Message);
    }
}

Thanks in advance

Hi,

in general there are two approaches. Either you subscribe to an event (or any other callback mechanism → observer pattern) or you frequently poll for the status.

Without trying it in code and by only looking at the GH api, the Locked property is part of a GH_Component. Probably you can listen to the ObjectChanged event and check if it’s Locked or not. Based on this you stop or start the timer. It should be this simple.

If this approach is not doable, you simply check the Locked state of the component in your OnElapsed handler. In this case the Timer should be active the entire runtime and only persist if the lock state is on false. This is a polling mechanism.

Note that event handling and asynchronous code execution might lead to unwanted side-effects if you are unsure about what is going on. So I would advise to log.

Hi @TomTom

Thanks a lot for your answer. I implemented it like this. It seems to work fine :slight_smile:

public AutoSaveComponent() : base("AutoSave", "AutoSave", "Automatically saves Grasshopper documents at a set interval.", "TEST", "Template")
{
    SetupTimer();
    // Subscribe to ObjectChanged event to monitor lock state
    this.ObjectChanged += OnObjectChanged;
}

// --- Initialize the autosave timer ---
private void SetupTimer()
{
    saveTimer = new Timer();
    saveTimer.Interval = IntervalMs;
    saveTimer.Tick += (s, e) => PerformAutoSave();
    saveTimer.Start();
}

// --- Event handler for component changes ---
private void OnObjectChanged(IGH_DocumentObject sender, GH_ObjectChangedEventArgs e)
{
    // Check the current lock state and manage timer accordingly
    if (this.Locked)
    {
        // Stop the timer when component is locked
        if (saveTimer.Enabled)
        {
            saveTimer.Stop();
            Rhino.RhinoApp.WriteLine("AutoSave: Timer stopped (component locked)");
        }
    }
    else
    {
        // Start the timer when component is unlocked
        if (!saveTimer.Enabled)
        {
            saveTimer.Start();
            Rhino.RhinoApp.WriteLine("AutoSave: Timer started (component unlocked)");
        }
    }
}