Проблема AppDomain, сериализации и System.Threading.Timer - PullRequest
0 голосов
/ 15 ноября 2011

У меня есть класс, который я создаю в AppDomain, используя CreateInstanceAndUnwrap. Класс содержит 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, таймер говорит, что Started = True в 15.11.2011 12:34:06

Я провел поиск в Google, MSDN и SO, но не нашел никакой информации, которая бы конкретно касалась комбинации AppDomain, Serialization и System.Threading.Timer. Также я не смог найти никакой информации о том, почему TimerCallback не может ссылаться на локальных членов класса, который создал экземпляр Timer.

Ответы [ 2 ]

3 голосов
/ 15 ноября 2011

Больше всего это вызвано различием между «маршалом по значению» (ваш класс) и «маршалом по ссылке» (скорее всего, что вы хотите). Если класс не является производным от MarshalByRefObject, он ведет себя как тип значений во время удаленного взаимодействия, то есть вы получаете копию объекта на каждой стороне связи. Если тип унаследован от MarshalByRefObject, вы получаете proxy на одной из сторон, которая не создала объект, и эта сторона сможет вызывать методы экземпляра в другом домене приложений.

Ссылки:

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

Управление временем жизни в статье о вызовах между приложениями в журнале MSDN - загрузите в декабре 2003 г. выпуск MSDN magazine (вам, вероятно, потребуется разблокировать контент в свойствах файла) или используйте ссылку веб-архива Управление сроком службы удаленных объектов .NET с помощью лизинга и спонсорства

0 голосов
/ 15 ноября 2011

Комментарий в вашем TimerMethod говорит:

// Started is always false here when this class is created within an AppDomain

Но ваш вывод говорит, что Started это правда.

Что это?

На самом деле,Я немного удивлен, что это работает, когда вы создаете локальный экземпляр.Конструктор Class1 создает таймер и возвращает его, но не устанавливает интервал или время, что означает, что таймер не срабатывает.

Когда вы вызываете Start, таймеринициализируется, но ему дается 1 секунда.Start возвращается, и вы звоните GetMessage, чтобы получить сообщение.Но если вы позвоните GetMessage до того, как таймер сможет выполнить свой обратный вызов, вы получите описанное вами поведение.

Если вы установите задержку в 1 секунду между вызовом Start извоня по номеру GetMessage, я думаю, вы увидите, что проблема заключается в ... синхронизации: вы пытаетесь получить сообщение до того, как таймер сможет его установить.Попробуйте следующее, чтобы проверить:

private void button1_Click(object sender, EventArgs e)
{
    _Class1.Start();
    Thread.Sleep(1000);
    MessageBox.Show(_Class1.GetMessage());
}

Или, я полагаю, вы можете просто попробовать нажать кнопку еще раз после задержки в пару секунд.

...