System.Threading.Timer 调用回调函数的隐式副本?

System.Threading.Timer Calling an Implicit Duplicate of a Callback Function?

我正在使用 WPF GUI(使用 MVVM)来控制嵌入式设备。到目前为止,该设备仍在开发中,目前还不能可靠地运行。因此,我创建了以下假设备:

interface IConnection
{
    bool IsValid { get; }
    bool Open();
    void Close();
    void Write(string message);
}

class SerialConnection : IConnection
{
    // Not yet implemented
}

class DevConnection : IConnection
{
    Timer _timer;
    Action<string> _callback;

    public bool IsValid {...}

    public DevConnection(Action<string> callback)
    {
        _timer = new Timer(tick, null, Timeout.Infinite, Timeout.Infinite);
        _callback = callback;
    }

    public bool Open() {...}
    public void Close() {...}
    public void Write(string Message) {...}

    private void tick(object args)
    {
        _callback("V01" + ToHex(vol1) + "\n");
        ...    
    }
}


Action<string> _callback;
是我的模型用来读取连接有效负载并适当更新其状态的函数

class Model
{
    IConnection _connection;

    public Model()
    {
        _connection = new DevConnection(Message);
    }

    private void Message(string payload)
    {
        ...
        _volume1 = floatValue;
        ...
    }
}

然而,当创建模型时,我在调用 Model.IConnection.Open() 启动计时器之前更改了其他地方的一堆属性。每次调用 Message() 回调时,调试器都会将模型显示为仍处于其原始构造状态。

1) 这里的幕后发生了什么? Threading.Timer 是否为其计数/滴答执行创建了一个新线程?如果是这样,为什么它要创建我的模型的默认副本 class?

2) 我该如何解决?我什至尝试为 DevConnection 提供我的模型 class 的副本以直接操作(不是我想要设置架构的方式),但它仍然导致相同的不良行为

不幸的是,我对线程理论只有初步的了解,不知道如何在 C# 中实现它。可悲的是,我怀疑这个问题是线程管理不善的结果。

鉴于神秘的"extra copies of the Model class"问题已解决。仍然存在如何从计时器预定回调安全地更新 UI 的问题。

正如@Frank J 所提到的,您的回调将在线程池线程上调用,而它只允许从 UI 线程的上下文中更新 UI 元素。这意味着如果它们直接或间接更新 UI 元素,您将需要将 Message 方法中执行的回调操作编组到 UI 线程上下文。

下面的代码片段展示了一种方法。

class Model
{
    private readonly SynchronizationContext _synchronizationContext;
    private readonly IConnection _connection;

    public Model()
    {
         // Capture UI synchronization context.
         // Note: this assumes that Model is constructed on the UI thread.
         _synchronizationContext = SynchronizationContext.Current; 
        _connection = new DevConnection(MessageCallback);
    }

    private void MessageCallback(string payload)
    {
        // schedule UI update on the UI thread.
        _synchronizationContext.Post(
            new SendOrPostCallback(ctx => Message(payload)),
            null);            
    }

    private void Message(string payload)
    {
        ...
        _volume1 = floatValue;
        ...
    }
}

还有一条建议:我认为 IConnection 应该是 IDisposable,因为您将不得不在某个地方处理计时器。