C# event debounce

C#EventsDebouncing

C# Problem Overview


I'm listening to a hardware event message, but I need to debounce it to avoid too many queries.

This is an hardware event that sends the machine status and I have to store it in a database for statistical purposes, and it sometimes happens that its status changes very often (flickering?). In this case I would like to store only a "stable" status and I want to implement it by simply waiting for 1-2s before storing the status to the database.

This is my code:

private MachineClass connect()
{
    try
    {
        MachineClass rpc = new MachineClass();
        rpc.RxVARxH += eventRxVARxH;
        return rpc;
    }
    catch (Exception e1)
    {
        log.Error(e1.Message);
        return null;
    }
}

private void eventRxVARxH(MachineClass Machine)
{
    log.Debug("Event fired");
}

I call this behaviour "debounce": wait a few times to really do its job: if the same event is fired again during the debounce time, I have to dismiss the first request and start to wait the debounce time to complete the second event.

What is the best choice to manage it? Simply a one-shot timer?

To explain the "debounce" function please see this javascript implementation for key events: http://benalman.com/code/projects/jquery-throttle-debounce/examples/debounce/

C# Solutions


Solution 1 - C#

I've used this to debounce events with some success:

public static Action<T> Debounce<T>(this Action<T> func, int milliseconds = 300)
{
	var last = 0;
	return arg =>
	{
		var current = Interlocked.Increment(ref last);
		Task.Delay(milliseconds).ContinueWith(task =>
		{
			if (current == last) func(arg);
			task.Dispose();
		});
	};
}

###Usage

Action<int> a = (arg) =>
{
    // This was successfully debounced...
	Console.WriteLine(arg);
};
var debouncedWrapper = a.Debounce<int>();

while (true)
{
	var rndVal = rnd.Next(400);
	Thread.Sleep(rndVal);
	debouncedWrapper(rndVal);
}

It may not be a robust as what's in RX but it's easy to understand and use.

Followup 2020-02-03

Revised @collie's solution using cancellation tokens as follows

public static Action<T> Debounce<T>(this Action<T> func, int milliseconds = 300)
{
    CancellationTokenSource? cancelTokenSource = null;

    return arg =>
    {
        cancelTokenSource?.Cancel();
        cancelTokenSource = new CancellationTokenSource();

        Task.Delay(milliseconds, cancelTokenSource.Token)
            .ContinueWith(t =>
            {
                if (t.IsCompletedSuccessfully)
                {
                    func(arg);
                }
            }, TaskScheduler.Default);
    };
}

Notes:

  • Calling Cancel is enough to dispose of the CTS
  • A successfully completed CTS is not canceled/disposed until the next call
  • As noted by @collie, tasks get disposed so no need to call Dispose on the task

I've not worked with cancellation tokens before and may not be using them correctly.

Solution 2 - C#

This isn't a trivial request to code from scratch as there are several nuances. A similar scenario is monitoring a FileSystemWatcher and waiting for things to quiet down after a big copy, before you try to open the modified files.

Reactive Extensions in .NET 4.5 were created to handle exactly these scenarios. You can use them easily to provide such functionality with methods like Throttle, Buffer, Window or Sample. You post the events to a Subject, apply one of the windowing functions to it, for example to get a notification only if there was no activity for X seconds or Y events, then subscribe to the notification.

Subject<MyEventData> _mySubject=new Subject<MyEventData>();
....
var eventSequenc=mySubject.Throttle(TimeSpan.FromSeconds(1))
                          .Subscribe(events=>MySubscriptionMethod(events));

Throttle returns the last event in a sliding window, only if there were no other events in the window. Any event resets the window.

You can find a very good overview of the time-shifted functions here

When your code receives the event, you only need to post it to the Subject with OnNext:

_mySubject.OnNext(MyEventData);

If your hardware event surfaces as a typical .NET Event, you can bypass the Subject and manual posting with Observable.FromEventPattern, as shown here:

var mySequence = Observable.FromEventPattern<MyEventData>(
    h => _myDevice.MyEvent += h,
    h => _myDevice.MyEvent -= h);  
_mySequence.Throttle(TimeSpan.FromSeconds(1))
           .Subscribe(events=>MySubscriptionMethod(events));

You can also create observables from Tasks, combine event sequences with LINQ operators to request eg: pairs of different hardware events with Zip, use another event source to bound Throttle/Buffer etc, add delays and a lot more.

Reactive Extensions is available as a NuGet package, so it's very easy to add them to your project.

Stephen Cleary's book "Concurrency in C# Cookbook" is a very good resource on Reactive Extensions among other things, and explains how you can use it and how it fits with the rest of the concurrent APIs in .NET like Tasks, Events etc.

Introduction to Rx is an excellent series of articles (that's where I copied the samples from), with several examples.

UPDATE

Using your specific example, you could do something like:

IObservable<MachineClass> _myObservable;

private MachineClass connect()
{

    MachineClass rpc = new MachineClass();
   _myObservable=Observable
                 .FromEventPattern<MachineClass>(
                            h=> rpc.RxVARxH += h,
                            h=> rpc.RxVARxH -= h)
                 .Throttle(TimeSpan.FromSeconds(1));
   _myObservable.Subscribe(machine=>eventRxVARxH(machine));
    return rpc;
}

This can be improved vastly of course - both the observable and the subscription need to be disposed at some point. This code assumes that you only control a single device. If you have many devices, you could create the observable inside the class so that each MachineClass exposes and disposes its own observable.

Solution 3 - C#

I ran into issues with this. I tried each of the answers here, and since I'm in a Xamarin universal app, I seem to be missing certain things that are required in each of these answers, and I didn't want to add any more packages or libraries. My solution works exactly how I'd expect it to, and I haven't run into any issues with it. Hope it helps somebody.

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

namespace OrderScanner.Models
{
    class Debouncer
    {
        private List<CancellationTokenSource> StepperCancelTokens = new List<CancellationTokenSource>();
        private int MillisecondsToWait;
        private readonly object _lockThis = new object(); // Use a locking object to prevent the debouncer to trigger again while the func is still running

        public Debouncer(int millisecondsToWait = 300)
        {
            this.MillisecondsToWait = millisecondsToWait;
        }

        public void Debouce(Action func)
        {
            CancelAllStepperTokens(); // Cancel all api requests;
            var newTokenSrc = new CancellationTokenSource();
            lock (_lockThis)
            {
                StepperCancelTokens.Add(newTokenSrc);
            }
            Task.Delay(MillisecondsToWait, newTokenSrc.Token).ContinueWith(task => // Create new request
            {
                if (!newTokenSrc.IsCancellationRequested) // if it hasn't been cancelled
                {
                    CancelAllStepperTokens(); // Cancel any that remain (there shouldn't be any)
                    StepperCancelTokens = new List<CancellationTokenSource>(); // set to new list
                    lock (_lockThis)
                    {
                        func(); // run
                    }
                }
            }, TaskScheduler.FromCurrentSynchronizationContext());
        }

        private void CancelAllStepperTokens()
        {
            foreach (var token in StepperCancelTokens)
            {
                if (!token.IsCancellationRequested)
                {
                    token.Cancel();
                }
            }
        }
    }
}

It's called like so...

private Debouncer StepperDeboucer = new Debouncer(1000); // one second

StepperDeboucer.Debouce(() => { WhateverMethod(args) });

I wouldn't recommend this for anything where the machine could be sending in hundreds of requests a second, but for user input, it works excellently. I'm using it on a stepper in an android/IOS app that calls to an api on step.

Solution 4 - C#

Recently I was doing some maintenance on an application that was targeting an older version of the .NET framework (v3.5).

I couldn't use Reactive Extensions nor Task Parallel Library, but I needed a nice, clean, consistent way of debouncing events. Here's what I came up with:

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

namespace MyApplication
{
    public class Debouncer : IDisposable
    {
        readonly TimeSpan _ts;
        readonly Action _action;
        readonly HashSet<ManualResetEvent> _resets = new HashSet<ManualResetEvent>();
        readonly object _mutex = new object();

        public Debouncer(TimeSpan timespan, Action action)
        {
            _ts = timespan;
            _action = action;
        }

        public void Invoke()
        {
            var thisReset = new ManualResetEvent(false);

            lock (_mutex)
            {
                while (_resets.Count > 0)
                {
                    var otherReset = _resets.First();
                    _resets.Remove(otherReset);
                    otherReset.Set();
                }

                _resets.Add(thisReset);
            }

            ThreadPool.QueueUserWorkItem(_ =>
            {
                try
                {
                    if (!thisReset.WaitOne(_ts))
                    {
                        _action();
                    }
                }
                finally
                {
                    lock (_mutex)
                    {
                        using (thisReset)
                            _resets.Remove(thisReset);
                    }
                }
            });
        }

        public void Dispose()
        {
            lock (_mutex)
            {
                while (_resets.Count > 0)
                {
                    var reset = _resets.First();
                    _resets.Remove(reset);
                    reset.Set();
                }
            }
        }
    }
}

Here's an example of using it in a windows form that has a search text box:

public partial class Example : Form 
{
    private readonly Debouncer _searchDebouncer;

    public Example()
    {
        InitializeComponent();
        _searchDebouncer = new Debouncer(TimeSpan.FromSeconds(.75), Search);
        txtSearchText.TextChanged += txtSearchText_TextChanged;
    }

    private void txtSearchText_TextChanged(object sender, EventArgs e)
    {
        _searchDebouncer.Invoke();
    }

    private void Search()
    {
        if (InvokeRequired)
        {
            Invoke((Action)Search);
            return;
        }

        if (!string.IsNullOrEmpty(txtSearchText.Text))
        {
            // Search here
        }
    }
}

Solution 5 - C#

RX is probably the easiest choice, especially if you're already using it in your application. But if not, adding it might be a bit of overkill.

For UI based applications (like WPF) I use the following class that use DispatcherTimer:

public class DebounceDispatcher
{
    private DispatcherTimer timer;
    private DateTime timerStarted { get; set; } = DateTime.UtcNow.AddYears(-1);

    public void Debounce(int interval, Action<object> action,
        object param = null,
        DispatcherPriority priority = DispatcherPriority.ApplicationIdle,
        Dispatcher disp = null)
    {
        // kill pending timer and pending ticks
        timer?.Stop();
        timer = null;

        if (disp == null)
            disp = Dispatcher.CurrentDispatcher;

        // timer is recreated for each event and effectively
        // resets the timeout. Action only fires after timeout has fully
        // elapsed without other events firing in between
        timer = new DispatcherTimer(TimeSpan.FromMilliseconds(interval), priority, (s, e) =>
        {
            if (timer == null)
                return;

            timer?.Stop();
            timer = null;
            action.Invoke(param);
        }, disp);

        timer.Start();
    }
}

To use it:

private DebounceDispatcher debounceTimer = new DebounceDispatcher();

private void TextSearchText_KeyUp(object sender, KeyEventArgs e)
{
    debounceTimer.Debounce(500, parm =>
    {
        Model.AppModel.Window.ShowStatus("Searching topics...");
        Model.TopicsFilter = TextSearchText.Text;
        Model.AppModel.Window.ShowStatus();
    });
}

Key events are now only processed after keyboard is idle for 200ms - any previous pending events are discarded.

There's also a Throttle method which always fires events after a given interval:

    public void Throttle(int interval, Action<object> action,
        object param = null,
        DispatcherPriority priority = DispatcherPriority.ApplicationIdle,
        Dispatcher disp = null)
    {
        // kill pending timer and pending ticks
        timer?.Stop();
        timer = null;

        if (disp == null)
            disp = Dispatcher.CurrentDispatcher;

        var curTime = DateTime.UtcNow;

        // if timeout is not up yet - adjust timeout to fire 
        // with potentially new Action parameters           
        if (curTime.Subtract(timerStarted).TotalMilliseconds < interval)
            interval = (int) curTime.Subtract(timerStarted).TotalMilliseconds;

        timer = new DispatcherTimer(TimeSpan.FromMilliseconds(interval), priority, (s, e) =>
        {
            if (timer == null)
                return;

            timer?.Stop();
            timer = null;
            action.Invoke(param);
        }, disp);

        timer.Start();
        timerStarted = curTime;            
    }

Solution 6 - C#

Panagiotis's answer is certainly correct, however I wanted to give a simpler example, as it took me a while to sort through how to get it working. My scenario is that a user types in a search box, and as the user types we want to make api calls to return search suggestions, so we want to debounce the api calls so they don't make one every time they type a character.

I'm using Xamarin.Android, however this should apply to any C# scenario...

private Subject<string> typingSubject = new Subject<string> ();
private IDisposable typingEventSequence;

private void Init () {
            var searchText = layoutView.FindViewById<EditText> (Resource.Id.search_text);
			searchText.TextChanged += SearchTextChanged;
			typingEventSequence = typingSubject.Throttle (TimeSpan.FromSeconds (1))
				.Subscribe (query => suggestionsAdapter.Get (query));
}

private void SearchTextChanged (object sender, TextChangedEventArgs e) {
			var searchText = layoutView.FindViewById<EditText> (Resource.Id.search_text);
			typingSubject.OnNext (searchText.Text.Trim ());
		}

public override void OnDestroy () {
			if (typingEventSequence != null)
				typingEventSequence.Dispose ();
			base.OnDestroy ();
		}

When you first initialize the screen / class, you create your event to listen to the user typing (SearchTextChanged), and then also set up a throttling subscription, which is tied to the "typingSubject".

Next, in your SearchTextChanged event, you can call typingSubject.OnNext and pass in the search box's text. After the debounce period (1 second), it will call the subscribed event (suggestionsAdapter.Get in our case.)

Lastly, when the screen is closed, make sure to dispose of the subscription!

Solution 7 - C#

This little gem is inspired by Mike Wards diabolically ingenious extension attempt. However, this one cleans up after itself quite nicely.

public static Action Debounce(this Action action, int milliseconds = 300)
{
    CancellationTokenSource lastCToken = null;

    return () =>
    {
        //Cancel/dispose previous
        lastCToken?.Cancel();
        try { 
            lastCToken?.Dispose(); 
        } catch {}          

        var tokenSrc = lastCToken = new CancellationTokenSource();

        Task.Delay(milliseconds).ContinueWith(task => { action(); }, tokenSrc.Token);
    };
}

Note: there's no need to dispose of the task in this case. See here for the evidence.

Usage

Action DebounceToConsole;
int count = 0;

void Main()
{
	//Assign
	DebounceToConsole = ((Action)ToConsole).Debounce(50);

	var random = new Random();
	for (int i = 0; i < 50; i++)
	{
		DebounceToConsole();
		Thread.Sleep(random.Next(100));
	}
}

public void ToConsole()
{
	Console.WriteLine($"I ran for the {++count} time.");
}

Solution 8 - C#

I needed something like this but in a web-application, so I can't store the Action in a variable, it will be lost between http requests.

Based on other answers and @Collie idea I created a class that looks at a unique string key for throttling.

public static class Debouncer
{
	static ConcurrentDictionary<string, CancellationTokenSource> _tokens = new ConcurrentDictionary<string, CancellationTokenSource>();
	public static void Debounce(string uniqueKey, Action action, int seconds)
	{
		var token = _tokens.AddOrUpdate(uniqueKey,
			(key) => //key not found - create new
			{
				return new CancellationTokenSource();
			},
			(key, existingToken) => //key found - cancel task and recreate
			{
				existingToken.Cancel(); //cancel previous
				return new CancellationTokenSource();
			}
		);

		Task.Delay(seconds * 1000, token.Token).ContinueWith(task =>
		{
			if (!task.IsCanceled)
			{
				action();
				_tokens.TryRemove(uniqueKey, out _);
			}
		}, token.Token);
	}
}

Usage:

//throttle for 5 secs if it's already been called with this KEY
Debouncer.Debounce("Some-Unique-ID", () => SendEmails(), 5);

As a side bonus, because it's based on a string key, you can use inline lambda's

Debouncer.Debounce("Some-Unique-ID", () => 
{
    //do some work here
}, 5);

Solution 9 - C#

I needed a Debounce method for Blazor and kept coming back to this page so I wanted to share my solution in case it helps others.

public class DebounceHelper
{
    private CancellationTokenSource debounceToken = null;

    public async Task DebounceAsync(Func<CancellationToken, Task> func, int milliseconds = 1000)
    {
        try
        {
            // Cancel previous task
            if (debounceToken != null) { debounceToken.Cancel(); }

            // Assign new token
            debounceToken = new CancellationTokenSource();

            // Debounce delay
            await Task.Delay(milliseconds, debounceToken.Token);

            // Throw if canceled
            debounceToken.Token.ThrowIfCancellationRequested();

            // Run function
            await func(debounceToken.Token);
        }
        catch (TaskCanceledException) { }
    }
}

Example call on a search function

<input type="text" @oninput=@(async (eventArgs) => await OnSearchInput(eventArgs)) />

@code {
    private readonly DebounceHelper debouncer = new DebounceHelper();

    private async Task OnSearchInput(ChangeEventArgs eventArgs)
    {
        await debouncer.DebounceAsync(async (cancellationToken) =>
        {
            // Search Code Here         
        });
    }
}

Solution 10 - C#

Created this class for solving it also for awaitable calls:

public class Debouncer
{
    private CancellationTokenSource _cancelTokenSource = null;

    public async Task Debounce(Func<Task> method, int milliseconds = 300)
    {
        _cancelTokenSource?.Cancel();
        _cancelTokenSource?.Dispose();

        _cancelTokenSource = new CancellationTokenSource();

        await Task.Delay(milliseconds, _cancelTokenSource.Token);

        await method();
    }
}

Sample of use:

private Debouncer _debouncer = new Debouncer();
....
await _debouncer.Debounce(YourAwaitableMethod);

Solution 11 - C#

This is inspired by Nieminen's Task.Delay-based Debouncer class. Simplified, some minor corrections, and should clean up after itself better.

class Debouncer: IDisposable
{
    private CancellationTokenSource lastCToken;
    private int milliseconds;

    public Debouncer(int milliseconds = 300)
    {
        this.milliseconds = milliseconds;
    }

    public void Debounce(Action action)
    {
        Cancel(lastCToken);

        var tokenSrc = lastCToken = new CancellationTokenSource();

        Task.Delay(milliseconds).ContinueWith(task =>
        {
             action();
        }, 
            tokenSrc.Token
        );
    }

    public void Cancel(CancellationTokenSource source)
    {
        if (source != null)
        {
            source.Cancel();
            source.Dispose();
        }                 
    }

    public void Dispose()
    {
        Cancel(lastCToken);
    }

    ~Debouncer()
    {
        Dispose();
    }
}

Usage

private Debouncer debouncer = new Debouncer(500); //1/2 a second
...
debouncer.Debounce(SomeAction);

Solution 12 - C#

Simply remember the latest 'hit:

DateTime latestHit = DatetIme.MinValue;

private void eventRxVARxH(MachineClass Machine)
{
    log.Debug("Event fired");
    if(latestHit - DateTime.Now < TimeSpan.FromXYZ() // too fast
    {
        // ignore second hit, too fast
        return;
    }
    latestHit = DateTime.Now;
    // it was slow enough, do processing
    ...
}

This will allow a second event if there was enough time after the last event.

Please note: it is not possible (in a simple way) to handle the last event in a series of fast events, because you never know which one is the last...

...unless you are prepared to handle the last event of a burst which is a long time ago. Then you have to remember the last event and log it if the next event is slow enough:

DateTime latestHit = DatetIme.MinValue;
Machine historicEvent;

private void eventRxVARxH(MachineClass Machine)
{
    log.Debug("Event fired");
 
    if(latestHit - DateTime.Now < TimeSpan.FromXYZ() // too fast
    {
        // ignore second hit, too fast
        historicEvent = Machine; // or some property
        return;
    }
    latestHit = DateTime.Now;
    // it was slow enough, do processing
    ...
    // process historicEvent
    ...
    historicEvent = Machine; 
}

Solution 13 - C#

I did some more simple solution based on @Mike Ward answer:

public static class CustomTaskExtension
{
    #region fields

    private static int _last = 0;

    #endregion

    public static void Debounce(CancellationTokenSource throttleCts, double debounceTimeMs, Action action)
    {
        var current = Interlocked.Increment(ref _last);
        Task.Delay(TimeSpan.FromMilliseconds(debounceTimeMs), throttleCts.Token)
            .ContinueWith(task =>
            {
                if (current == _last) action();
                task.Dispose();
            });
    }
}

Example how to use it:

// security way to cancel the debounce process any time
CancellationTokenSource _throttleCts = new CancellationTokenSource();

public void MethodCalledManyTimes()
{
    // will wait 250ms after the last call
    CustomTaskExtension.Debounce(_throttleCts, 250, async () =>
    {
        Console.Write("Execute your code 250ms after the last call.");
    });
}

Solution 14 - C#

I came up with this in my class definition.

I wanted to run my action immediately if there hasn't been any action for the time period (3 seconds in the example).

If something has happened in the last three seconds, I want to send the last thing that happened within that time.

    private Task _debounceTask = Task.CompletedTask;
    private volatile Action _debounceAction;

    /// <summary>
    /// Debounces anything passed through this 
    /// function to happen at most every three seconds
    /// </summary>
    /// <param name="act">An action to run</param>
    private async void DebounceAction(Action act)
    {
        _debounceAction = act;
        await _debounceTask;

        if (_debounceAction == act)
        {
            _debounceTask = Task.Delay(3000);
            act();
        }
    }

So, if I have subdivide my clock into every quarter of a second

  TIME:  1e&a2e&a3e&a4e&a5e&a6e&a7e&a8e&a9e&a0e&a
  EVENT:  A         B    C   D  E              F  
OBSERVED: A           B           E            F

Note that no attempt is made to cancel the task early, so it's possible for actions to pile up for 3 seconds before eventually being available for garbage collection.

Solution 15 - C#

Figured out how to use System.Reactive NuGet package for doing a proper debouncing on a TextBox.

At the class level, we have our field

private IObservable<EventPattern<TextChangedEventArgs>> textChanged;

Then when we want to start listening to the event:

// Debouncing capability
textChanged = Observable.FromEventPattern<TextChangedEventArgs>(txtSearch, "TextChanged");
textChanged.ObserveOnDispatcher().Throttle(TimeSpan.FromSeconds(1)).Subscribe(args => {
    Debug.WriteLine("bounce!");
});

Make sure you don't also wire your textbox up to an event handler. The Lambda above is the event handler.

Solution 16 - C#

I wrote an async debouncer that doesn't run async-in-sync.

public sealed class Debouncer : IDisposable {

  public Debouncer(TimeSpan? delay) => _delay = delay ?? TimeSpan.FromSeconds(2);

  private readonly TimeSpan _delay;
  private CancellationTokenSource? previousCancellationToken = null;

  public async Task Debounce(Action action) {
    _ = action ?? throw new ArgumentNullException(nameof(action));
    Cancel();
    previousCancellationToken = new CancellationTokenSource();
    try {
      await Task.Delay(_delay, previousCancellationToken.Token);
      await Task.Run(action, previousCancellationToken.Token);
    }
    catch (TaskCanceledException) { }    // can swallow exception as nothing more to do if task cancelled
  }

  public void Cancel() {
    if (previousCancellationToken != null) {
      previousCancellationToken.Cancel();
      previousCancellationToken.Dispose();
    }
  }

  public void Dispose() => Cancel();

}

I use it to debounce changes reported on file changes, see complete example here.

Solution 17 - C#

I was inspired by Mike's answer, but needed solution that worked without tasks, which simply swallows subsequent event invocations until debounce time-out runs out. Here's my solution:

public static Action<T> Debounce<T>(this Action<T> action, int milliseconds = 300)
{
    DateTime? runningCallTime = null;
    var locker = new object();

    return arg =>
    {
        lock (locker)
        {
            if (!runningCallTime.HasValue ||
                runningCallTime.Value.AddMilliseconds(milliseconds) <= DateTime.UtcNow)
            {
                runningCallTime = DateTime.UtcNow;
                action.Invoke(arg);
            }
        }
    };

}

Solution 18 - C#

Another implementation

public static class Debounce
{
    public static Action Action(Action action, TimeSpan time)
    {
        var timer = new Timer(_ => action(), null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);

        return () => timer.Change(time, Timeout.InfiniteTimeSpan);
    }
}

Solution 19 - C#

I know I'm a couple hundred thousand minutes late to this party but I figured I'd add my 2 cents. I'm surprised no one has suggested this so I'm assuming there's something I don't know that might make it less than ideal so maybe I'll learn something new if this gets shot down. I often use a solution that uses the System.Threading.Timer's Change() method.

using System.Threading;

Timer delayedActionTimer;

public MyClass()
{
    // Setup our timer
    delayedActionTimer = new Timer(saveOrWhatever, // The method to call when triggered
                                   null, // State object (Not required)
                                   Timeout.Infinite, // Start disabled
                                   Timeout.Infinite); // Don't repeat the trigger
}

// A change was made that we want to save but not until a
// reasonable amount of time between changes has gone by
// so that we're not saving on every keystroke/trigger event.
public void TextChanged()
{
    delayedActionTimer.Change(3000, // Trigger this timers function in 3 seconds,
                                    // overwriting any existing countdown
                              Timeout.Infinite); // Don't repeat this trigger; Only fire once
}

// Timer requires the method take an Object which we've set to null since we don't
// need it for this example
private void saveOrWhatever(Object obj) 
{
    /*Do the thing*/
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTobiaView Question on Stackoverflow
Solution 1 - C#Mike WardView Answer on Stackoverflow
Solution 2 - C#Panagiotis KanavosView Answer on Stackoverflow
Solution 3 - C#NieminenView Answer on Stackoverflow
Solution 4 - C#Ronnie OverbyView Answer on Stackoverflow
Solution 5 - C#Rick StrahlView Answer on Stackoverflow
Solution 6 - C#JustinView Answer on Stackoverflow
Solution 7 - C#CollieView Answer on Stackoverflow
Solution 8 - C#Alex from JitbitView Answer on Stackoverflow
Solution 9 - C#Douglas RiddleView Answer on Stackoverflow
Solution 10 - C#Daniel LopesView Answer on Stackoverflow
Solution 11 - C#CollieView Answer on Stackoverflow
Solution 12 - C#DrKochView Answer on Stackoverflow
Solution 13 - C#Ângelo PolottoView Answer on Stackoverflow
Solution 14 - C#Anthony WieserView Answer on Stackoverflow
Solution 15 - C#tolsen64View Answer on Stackoverflow
Solution 16 - C#lonixView Answer on Stackoverflow
Solution 17 - C#Peter 375View Answer on Stackoverflow
Solution 18 - C#Johan LarssonView Answer on Stackoverflow
Solution 19 - C#HDL_CinC_DragonView Answer on Stackoverflow