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
,因为您将不得不在某个地方处理计时器。
我正在使用 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
,因为您将不得不在某个地方处理计时器。