При использовании System.Timers.Timer используйте свойство SynchronizingObject
таймера. Очевидно, это вызывает метод, который обрабатывает событие Elapsed, для вызова в том же потоке, в котором был создан назначенный компонент (SynchronizingObject).
например. если myButton - это элемент управления вашей формы (независимо от основного потока GUI),
System.Timers.Timer myTimer = new System.Timers.Timer();
myTimer.SynchronizingObject = this.myButton;
это приводит к тому, что обработчик Elapsed запускается в том же потоке, и устраняет некоторые ошибки «операции с несколькими потоками».
Пожалуйста, обратите внимание: я очень мало знаю о том, является ли это потокобезопасным, но работал нормально для меня в конкретном случае использования. Надеюсь, это поможет в любом случае.