AppDomain、序列化和 System.Threading.Timer 问题
Posted
技术标签:
【中文标题】AppDomain、序列化和 System.Threading.Timer 问题【英文标题】:AppDomain, Serialization and System.Threading.Timer issue 【发布时间】:2011-11-15 17:50:55 【问题描述】:我有一个使用 CreateInstanceAndUnwrap 在 AppDomain 中创建的类。该类包含一个 System.Threading.Timer。
我遇到的问题是,当类被实例化时,计时器的回调方法似乎看不到类实例的正确值。
我有下面的示例代码来说明问题:
库类
using System;
using System.Threading;
namespace Library
[Serializable]
public class Class1
public Class1()
Started = false;
_Timer = new Timer(TimerMethod);
public bool Started get; set;
private readonly Timer _Timer;
private string _Message;
private string _TimerMessage;
public bool Start()
Started = true;
_Message = string.Format("Class1 says Started = 0", Started);
_TimerMessage = "Timer message not set yet";
_Timer.Change(1000, 1000);
return Started;
public string GetMessage()
// _TimerMessage is never set by TimerMethod when this class is created within an AppDomain
return string.Format("0, 1", _Message, _TimerMessage);
public void TimerMethod(object state)
// Started is always false here when this class is created within an AppDomain
_TimerMessage = string.Format("Timer says Started = 0 at 1", Started, DateTime.Now);
消费类
using System;
using System.Windows.Forms;
using Library;
namespace GUI
public partial class Form1 : Form
public Form1()
InitializeComponent();
var appDomainSetup = new AppDomainSetup
ApplicationName = "GUI",
ApplicationBase = AppDomain.CurrentDomain.BaseDirectory
;
_AppDomain = AppDomain.CreateDomain(appDomainSetup.ApplicationName,
AppDomain.CurrentDomain.Evidence,
appDomainSetup);
_Class1 = _AppDomain.CreateInstanceAndUnwrap("Library", "Library.Class1") as Class1;
private readonly AppDomain _AppDomain;
private readonly Class1 _Class1;
private void button1_Click(object sender, EventArgs e)
_Class1.Start();
MessageBox.Show(_Class1.GetMessage());
private void button2_Click(object sender, EventArgs e)
MessageBox.Show(_Class1.GetMessage());
当上面的代码运行时,GetMessage()
总是返回:
Class1 说 Started = True,尚未设置计时器消息
但是,如果我改成上面表格的构造函数来创建Class1的本地实例,
public Form1()
InitializeComponent();
_Class1 = new Class1();
GetMessage()
返回预期的消息:
Class1 说 Started = True,Timer 说 Started = True 于 2011 年 11 月 15 日下午 12:34:06
我搜索了 Google、MSDN 和 SO,但没有找到任何专门针对 AppDomain、Serialization 和 System.Threading.Timer 组合的信息。我也找不到任何关于 TimerCallback 为何无法引用实例化 Timer 的类的本地成员的信息。
【问题讨论】:
当您将调试器放在“_TimerMessage = string.Format("Timer says Started = 0 at 1", Started, DateTime.Now);"代码执行到那里了吗? 让 Class1 从 MarshalByRefObject 派生来解决问题。 @slfan,在我的实际课程中,我还有其他不可序列化的类(例如 XElement)。对于这些类,我在运行时收到一个 SerializationException,上面写着“Type 'System.Xml.Linq.XElement' ...未标记为可序列化。”我本来希望 Timer 类也是如此。 @kurtnelle,是的,我可以在计时器回调例程中设置一个断点,当我检查 Started 时,它是 false。 【参考方案1】:这很可能是由“按值编组”(您的班级)和“按引用编组”(很可能是您想要的)之间的差异引起的。如果类不是从 MarshalByRefObject 派生的,那么它在远程处理期间的行为类似于值类型,这意味着您在通信的每一端都获得对象的 副本。如果类型从 MarshalByRefObject 派生,那么您在未实例化对象的一侧获得 proxy,并且该一侧将能够调用另一 AppDomain 中实例上的方法。
链接:
MarshalByRefObject - http://msdn.microsoft.com/en-us/library/system.marshalbyrefobject.aspx
MSDN 杂志中跨应用程序域调用期间的生命周期管理文章 - 下载 2003 年 12 月发行的 MSDN magazine(您可能需要取消阻止文件属性中的内容)或使用 Web 存档链接 Managing the Lifetime of Remote .NET Objects with Leasing and Sponsorship
【讨论】:
我对我的示例代码进行了建议的更改,并且成功了。但是,当我对我的生产代码进行相同的更改时,我收到了我的消费者类的 SerializationException。在示例中,消费者类是一个表单,但在我的生产代码中,消费者类是另一个包含许多不可序列化成员的类。我决定也尝试从 MarshalByRefObject 降级我的消费者类,这似乎已经成功了。谢谢。 另外,感谢 LifeTime 管理参考!这将非常有帮助。 杂志链接不再有效 - 根据***.com/a/25315226/155892,它位于 2003 年 12 月 期,但可以从同一页面以 CHM 格式下载。【参考方案2】:TimerMethod
中的评论说:
// Started is always false here when this class is created within an AppDomain
但是你的输出表明Started
是真的。
这是什么?
实际上,当您创建本地实例时它可以工作,我有点惊讶。 Class1
构造函数创建计时器并给它一个回调,但它没有设置间隔或到期时间,这意味着计时器不会触发。
当您调用Start
时,计时器会被初始化,但会给出 1 秒的到期时间。 Start
返回,您致电 GetMessage
获取消息。但是,如果您在计时器有机会执行其回调之前调用GetMessage
,您将获得您所描述的行为。
如果您在调用 Start
和调用 GetMessage
之间延迟 1 秒,我想您会发现问题在于……时间:您试图在计时器之前收到消息有机会设置它。请尝试以下方法进行验证:
private void button1_Click(object sender, EventArgs e)
_Class1.Start();
Thread.Sleep(1000);
MessageBox.Show(_Class1.GetMessage());
或者,我想您可以在延迟几秒钟后再次尝试单击该按钮。
【讨论】:
button1_Click 返回的消息不是问题。该消息是预期的,因为计时器尚未触发。几秒钟后来自 button2_Click 的消息显示了问题。当在 AppDomain 中创建 Class1 时,在 TimerMethod always 中设置断点显示 Start 为 false。以上是关于AppDomain、序列化和 System.Threading.Timer 问题的主要内容,如果未能解决你的问题,请参考以下文章
AppDomain CreateInstanceAndUnwrap:类型未标记为可序列化