ritnet.ru

9 Декабрь 2008

Синхронизация событий

Рубрика: Новости — admin @ 12:18

Как следует поступить, если код должен быть выполнен после наступления нескольких разноплановых событий, таких как, например, успешная инициализация формы, загрузка данных из удаленного источника, либо окончание работы другого потока?

Для решения этой проблемы я использую достаточно простой класс Synchronizer:

///<summary>
/// Класс, позволяющий получить доступ к точке, когда все события синхронизованны.
///</summary>
public class Synchronizer
{
    /// <summary>
    /// Событие наступает, когда элемент синхронизирован.
    /// </summary>
    public event EventHandler Synchronized;

    /// <summary>
    /// Событие наступает, если при загрузке одного из элементов возникла ошибка.
    /// </summary>
    public event SynchronizationErrorEventHandler SynchronizationError;

    private readonly object _locker;
    private uint _count;

    private Timer _timer;
    private System.Threading.Timer _t;

    /// <summary>
    /// Создаёт новый объект.
    /// </summary>
    /// <param name="elementsCount">Количество событий, при наступлении которых наступит синхронизация.</param>
    public Synchronizer(uint elementsCount)
    {
        _locker = new object();
        Refresh(elementsCount);
    }

    /// <summary>
    /// Создаёт новый объект.
    /// </summary>
    /// <param name="elementsCount">Количество событий, при наступлении которых наступит синхронизация.</param>
    /// <param name="timeout">Значение таймаута.</param>
    public Synchronizer(uint elementsCount, TimeSpan timeout)
    {
        _locker = new object();
        Refresh(elementsCount, timeout);
    }

    /// <summary>
    /// Указывает, что наступило наблюдаемое событие.
    /// </summary>
    public void ElementReady()
    {
        lock (_locker)
        {
            if (_count == 0)
                throw new InvalidOperationException("Синхронизация всех элементов уже наступила.");

            _count -= 1;
            if (_count == 0)
            {
                FireSynchronization();
            }
        }
    }

    /// <summary>
    /// Принудительно уведомляет о синхронизации.
    /// </summary>
    public void ForceSynchronization()
    {
        lock (_locker)
        {
            if (_count == 0)
                throw new InvalidOperationException("Синхронизация всех элементов уже наступила.");

            _count = 0;
            FireSynchronization();
        }
    }

    /// <summary>
    /// Устанавливает новое значение счетчика.
    /// </summary>
    /// <param name="count">Количество событий, которое должно наступить для синхронизации.</param>
    public void Refresh(uint count)
    {
        lock (_locker)
        {
            if (count == 0)
            {
                throw new ArgumentException("Количество элементов должно быть больше нуля.");
            }
            _count = count;

            ShutdownTimer();
        }
    }

    /// <summary>
    /// Устанавливает новое значение счетчика.
    /// </summary>
    /// <param name="count">Количество событий, которое должно наступить для синхронизации.</param>
    /// <param name="timeout">Значение таймаута.</param>
    public void Refresh(uint count, TimeSpan timeout)
    {
        lock (_locker)
        {
            Refresh(count);
            if(_timer == null)
            {
                _timer = new Timer(timeout.TotalMilliseconds);
                _timer.AutoReset = false;
                _timer.Elapsed += TimeoutElapsed;
                _timer.Start();
            }
        }
    }

    /// <summary>
    /// Указывает, что возникла ошибка при попытке обеспечить доступ к одному из элементов.
    /// </summary>
    /// <param name="message">Сообщение об ошибке.</param>
    public void ElementError(string message)
    {
        lock (_locker)
        {
            if (SynchronizationError != null)
                SynchronizationError(this, new SynchronizationErrorEventArgs(message, SynchronizationErrorReason.Error));
        }
    }

    /// <summary>
    /// Указывает, что возникла ошибка при попытке обеспечить доступ к одному из элементов.
    /// </summary>
    /// <param name="ex">Исключение, являющееся причиной ошибки.</param>
    public void ElementError(Exception ex)
    {
        lock (_locker)
        {
            if (SynchronizationError != null)
                SynchronizationError(this, new SynchronizationErrorEventArgs(ex));
        }
    }

    /// <summary>
    /// Указывает, что возникла ошибка при попытке обеспечить доступ к одному из элементов.
    /// </summary>
    /// <param name="ex">Исключение, являющееся причиной ошибки.</param>
    /// <param name="message">Сообщение об ошибке.</param>
    public void ElementError(Exception ex, string message)
    {
        lock (_locker)
        {
            if (SynchronizationError != null)
                SynchronizationError(this, new SynchronizationErrorEventArgs(ex, message));
        }
    }

    /// <summary>
    /// Функция вызывается при таймауте.
    /// </summary>
    private void TimeoutElapsed(object sender, ElapsedEventArgs e)
    {
        lock (_locker)
        {
            if (SynchronizationError != null)
                SynchronizationError(this, new SynchronizationErrorEventArgs("Timeout", SynchronizationErrorReason.Timeout));
        }
    }

    /// <summary>
    /// Уведомляет о синхронизации.
    /// </summary>
    private void FireSynchronization()
    {
        lock (_locker)
        {
            ShutdownTimer();

            if (Synchronized != null)
                Synchronized(this, EventArgs.Empty);
        }
    }

    /// <summary>
    /// Останавливает таймер.
    /// </summary>
    private void ShutdownTimer()
    {
        if (_timer != null)
        {
            _timer.Stop();
            _timer.Dispose();
            _timer = null;
        }
    }
}

Код вспомогательных классов:

Перечисление причин ошибки синхронизации

/// <summary>
/// Перечисление, указывающее на причину ошибки синхронизации.
/// </summary>
public enum SynchronizationErrorReason
{
    /// <summary>
    /// Ошибка по таймауту.
    /// </summary>
    Timeout,
    /// <summary>
    /// В связи с выброшенным исключением.
    /// </summary>
    Error
}

Событие ошибки синхронизации

public delegate void SynchronizationErrorEventHandler(Synchronizer sender, SynchronizationErrorEventArgs e);

/// <summary>
/// Аргументы ошибки синхронизации.
/// </summary>
public class SynchronizationErrorEventArgs : EventArgs
{
    /// <summary>
    /// Инициализирует новый объект.
    /// </summary>
    /// <param name="message">Строка сообщения об ошибке.</param>
    /// <param name="reason">Причина ошибки.</param>
    public SynchronizationErrorEventArgs(string message, SynchronizationErrorReason reason)
    {
        Message = message;
        Reason = reason;
    }

    /// <summary>
    /// Инициализирует новый объект.
    /// </summary>
    /// <param name="ex">Исключение, из-за которого возникла ошибка синхронизации.</param>
    /// <param name="message">Сообщение об ошибке.</param>
    public SynchronizationErrorEventArgs(Exception ex, string message)
    {
        Exception = ex;
        Message = message;
        Reason = SynchronizationErrorReason.Error;
    }

    /// <summary>
    /// Инициализирует новый объект.
    /// </summary>
    /// <param name="ex">Исключение, из-за которого возникла ошибка синхронизации.</param>
    public SynchronizationErrorEventArgs(Exception ex)
    {
        Exception = ex;
        Message = ex.Message;
        Reason = SynchronizationErrorReason.Error;
    }

    /// <summary>
    /// Возвращает исключение.
    /// </summary>
    public Exception Exception
    {
        get;
        private set;
    }

    /// <summary>
    /// Возвращает причину ошибки.
    /// </summary>
    public SynchronizationErrorReason Reason
    {
        get;
        private set;
    }

    /// <summary>
    /// Возвращает описание ошибки.
    /// </summary>
    public string Message
    {
        get;
        private set;
    }
}

Как видно из представленного кода, класс Synchronizer потокобезопасен. Использовать его достаточно просто:

class Program
{
    static void Main(string[] args)
    {
        //Указываем количество элементов, требующих синхронизации и (опционально) таймаут.
        Synchronizer synch = new Synchronizer(2, TimeSpan.FromSeconds(3));
        //Прикрепляем события.
        synch.Synchronized += new EventHandler(synch_Synchronized);
        synch.SynchronizationError += new SynchronizationErrorEventHandler(synch_SynchronizationError);
        //Приостанавливаем работу потока для получения ошибки таймаута.
        Thread.Sleep(4000);
        //Обновляем счетчик класса.
        synch.Refresh(1);
        //Выполняем длительную работу.
        Thread.Sleep(2000);
        //Уведомляем о синхронизации.
        synch.ElementReady();
        Console.WriteLine("Complete");
    }

    static void synch_SynchronizationError(Synchronizer sender, SynchronizationErrorEventArgs e)
    {
        Console.WriteLine("Message: " + e.Message);
        Console.WriteLine("Reason: " + e.Reason);
    }

    static void synch_Synchronized(object sender, EventArgs e)
    {
        Console.WriteLine("Synchronized");
    }
}

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

Комментариев нет

Комментариев нет.

RSS-лента комментариев к этой записи.

Извините, обсуждение на данный момент закрыто.

Сайт работает на WordPress