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

时间:2022-11-23 20:38:48

I am working on a WPF GUI (using MVVM) to control an embedded device. As of yet, the device is still in development and not currently functioning reliably. As such I have created the following fake device:

我正在使用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");
        ...    
    }
}

The
Action<string> _callback;
is the function used by my model to read the payload of the connection and update its state appropriately

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

class Model
{
    IConnection _connection;

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

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

However when the Model is created, I change a bunch of the properties elsewhere before calling Model.IConnection.Open() to start the timer. Every time the Message() callback is called, the debugger shows the Model as still being in its original, constructed state.

但是,在创建Model时,我会在调用Model.IConnection.Open()以启动计时器之前更改其他一些属性。每次调用Message()回调时,调试器都会将Model显示为仍处于其原始构造状态。

1) What is going on behind the scenes here? Is the Threading.Timer creating a new thread for its counting / tick execution? If so, why is it creating a default copy of my Model class?

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

2) How do I fix it? I even tried giving the DevConnection a copy of my Model class to operate on directly (not how I'd like to setup the architecture) and it still resulted in the same undesired behavior

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

Unfortunately I have only a rudimentary understanding of the theory of threading, with no idea how to implement it in C#. Tragically I suspect that this issue is a result of thread mis-management.

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

1 个解决方案

#1


1  

Given that the mysterious "extra copies of the Model class" issue was solved. There remains the issue of how you can safely update your UI from a timer scheduled callback.

鉴于神秘的“模型类的额外副本”问题得到了解决。仍然存在如何从计时器预定回调安全地更新UI的问题。

As was mentioned by @Frank J, your callback will be invoked on a thread pool thread, whereas it is only allowed to update UI elements from the context of the UI thread. This means you will need to marshal the callback's actions performed in the Message method to the UI thread context if they directly or indirectly update UI elements.

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

The code snippet below shows one way of doing that.

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

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;
        ...
    }
}

One more piece of advice: I think IConnection should be an IDisposable because you will have to dispose of the timer somewhere.

还有一条建议:我认为IConnection应该是IDisposable,因为你必须在某处处理定时器。

#1


1  

Given that the mysterious "extra copies of the Model class" issue was solved. There remains the issue of how you can safely update your UI from a timer scheduled callback.

鉴于神秘的“模型类的额外副本”问题得到了解决。仍然存在如何从计时器预定回调安全地更新UI的问题。

As was mentioned by @Frank J, your callback will be invoked on a thread pool thread, whereas it is only allowed to update UI elements from the context of the UI thread. This means you will need to marshal the callback's actions performed in the Message method to the UI thread context if they directly or indirectly update UI elements.

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

The code snippet below shows one way of doing that.

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

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;
        ...
    }
}

One more piece of advice: I think IConnection should be an IDisposable because you will have to dispose of the timer somewhere.

还有一条建议:我认为IConnection应该是IDisposable,因为你必须在某处处理定时器。