System.Threading.Timer调用回调函数的隐式复制?

时间:2015-04-02 17:29:05

标签: c# wpf multithreading mvvm timer

我正在使用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时,我会在调用Model.IConnection.Open()来启动计时器之前更改其他一些属性。每次调用Message()回调时,调试器都会将Model显示为仍处于其原始构造状态。

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

2)我该如何解决?我甚至尝试给DevConnection一个我的Model类的副本来直接操作(而不是我喜欢如何设置架构),它仍然导致了同样的不良行为

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

1 个答案:

答案 0 :(得分:1)

鉴于神秘的“Model类”问题的额外副本已经解决。仍存在如何从计时器预定回调安全地更新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,因为你必须在某个地方处理计时器。