You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# WPF中System.Timers.Timer引发InvalidOperationException问题

问题分析与解决方案

问题根源

System.Timers.Timer的Elapsed事件是在线程池线程中触发的,因此Recorder.StateChanged事件会在非UI线程上触发。而WPF中ICommand.RaiseCanExecuteChanged()方法必须在UI线程调用,否则会抛出InvalidOperationException,或者导致命令关联的按钮状态更新异常。


解决方案

方案1:为Timer设置SynchronizingObject

System.Timers.Timer的SynchronizingObject属性可以指定一个UI线程的控件(如Window、UserControl),这样Elapsed事件会自动切换到UI线程执行,StateChanged事件也会在UI线程触发,无需额外处理线程切换。

  1. 给Recorder类添加设置SynchronizingObject的方法(因为Timer是私有成员):
internal class Recorder
{
    // ... 原有代码 ...

    public void SetTimerSynchronizingObject(ISynchronizeInvoke syncObj)
    {
        Timer.SynchronizingObject = syncObj;
    }
}
  1. 在View的代码后台(如MainWindow.xaml.cs)中初始化Recorder时,传入当前UI控件:
var recorder = new Recorder(feed, data, isLooped);
recorder.SetTimerSynchronizingObject(this); // this指代当前Window,继承自ISynchronizeInvoke
var vm = new RecorderViewModel(recorder);

方案2:在ViewModel中切换到UI线程更新命令状态

如果无法修改Recorder类的设计,就在StateChanged事件处理方法中,通过Dispatcher切换到UI线程调用RaiseCanExecuteChanged。注意用Invoke(同步)而非BeginInvoke,确保状态更新的及时性:

void OnStateChanged(object? sender, RecorderState state)
{
    Application.Current.Dispatcher.Invoke(() =>
    {
        PauseCommandField.RaiseCanExecuteChanged();
        ResetCommandField.RaiseCanExecuteChanged();
    });
}

如果你的ViewModel继承自DependencyObject,也可以直接用自身的Dispatcher:

this.Dispatcher.Invoke(() => { /* ... */ });

方案3:改用WPF专属的DispatcherTimer

如果Recorder类仅在WPF环境中使用,直接将System.Timers.Timer替换为DispatcherTimer,它的Tick事件默认在UI线程触发,彻底避免线程问题:

internal class Recorder
{
    const double TimeIntervalMilliseconds = 20;
    readonly DispatcherTimer Timer;
    // ... 其他成员不变 ...

    public Recorder(IFeed feed, ISessionDataSink data, bool isLooped)
    {
        Feed = feed;
        Data = data;
        IsLooped = isLooped;
        state = RecorderState.Initial;
        Timer = new DispatcherTimer
        {
            Interval = TimeSpan.FromMilliseconds(TimeIntervalMilliseconds),
            AutoReset = true
        };
        Timer.Tick += OnTimeElapsed;
    }

    // 调整OnTimeElapsed的方法签名
    void OnTimeElapsed(object? sender, EventArgs e)
    {
        // 原有定时逻辑不变
    }
}

内容的提问来源于stack exchange,提问作者Software Carpenter

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 07:42:55