Search code examples
c#serialization.net-4.0timerappdomain

AppDomain, Serialization and System.Threading.Timer issue


I have a class that I am creating within an AppDomain using CreateInstanceAndUnwrap. The class contains a System.Threading.Timer.

The problem that I am having is that when the class is instantiated, the timer's callback method can't seem to see proper values of the class instance.

I have sample code below that illustrates the problem:

Library Class

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

Consumer Class

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());
        }
    }
}

When the code above is run, GetMessage() always returns:

Class1 says Started = True, Timer message not set yet

However, if I change to the constructor of the form above to create a local instance of Class1,

        public Form1()
        {
            InitializeComponent();

            _Class1 = new Class1();
        }

GetMessage() returns the expected message:

Class1 says Started = True, Timer says Started = True at 11/15/2011 12:34:06 PM

I have searched Google, MSDN and SO, but haven't found any information that specifically addresses the combination of AppDomain, Serialization and the System.Threading.Timer. Nor could I find any information on why the TimerCallback could not reference local members of the class that instantiated the Timer.


Solution

  • Most likley it is caused by difference between "marshal by value" (your class) and "marshal by reference" (most likely what you want). If class is not derived from MarshalByRefObject than it behaves like values type during remoting, meaning you get copy of the object on each side of communication. If type derives from MarshalByRefObject than you get proxy on one of the side that did not instanitated the object and that side will be able to call methods on the instance in the other AppDomain.

    Links:

    MarshalByRefObject - http://msdn.microsoft.com/en-us/library/system.marshalbyrefobject.aspx

    Lifetime management during cross-app-domain calls article in MSDN magazine - download December 2003 issue of MSDN magazine (you'd likely need to unblock content in file's properties) or use Web archive link Managing the Lifetime of Remote .NET Objects with Leasing and Sponsorship