SynchronizingObject vs. Invoke - PullRequest
       1

SynchronizingObject vs. Invoke

3 голосов
/ 31 января 2011

В классе моей формы, я добавил метод для "исчезновения" его.Это использует System.Timers.Timer, а событие Elapsed использует делегата для изменения прозрачности формы.Это был код:

public void FadeOut()
{
    // Timer for transition
    Timer fade = new Timer();

    // Transition code
    fade.Elapsed += delegate
    {
        this.Opacity += 0.05;

        if (this.Opacity >= .95)
        {
            this.Opacity = 1;
            fade.Enabled = false;
            fade.Dispose();
        }
    };

    fade.Interval = 100;
    fade.Enabled = true;
}

Это вызвало ошибку «Операция с несколькими потоками не действительна», что является обычным препятствием, которое я вижу.Поэтому я искал решения, и первые из них были связаны с использованием .BeginInvoke и блоков кода, чтобы сохранить вызов того же потока, что и элемент управления.Но я обнаружил, что это выглядело действительно громоздко, поэтому я продолжал искать, а затем обнаружил свойство SynchronizingObject System.Timers.Timer.Это кажется лучше, потому что ему нужна была только одна дополнительная строка кода:

// Timer for transition
Timer fade = new Timer();
fade.SynchronizingObject = this;

Теперь код работает нормально.Но я действительно сбит с толку, почему многие решения предлагают использовать BeginInvoke/Invoke, когда все, что нужно, это установить SynchronizingObject для управления формой?

Ответы [ 5 ]

3 голосов
/ 31 января 2011

В основном потому, что бессмысленно использовать собственность.Да, это гарантирует, что обработчик событий Elapsed работает в потоке пользовательского интерфейса.Но теперь он делает то же самое, что и System.Windows.Forms.Timer.

Не совсем, но хуже.Потому что это не гарантирует, что Elapsed не будет вызываться после его отключения.Его отключение не сбрасывает ни ожидающие вызовы, ни потоки TP, которые еще не готовы к запуску.Могут быть сотни, если интервал мал по сравнению с объемом работы, выполненной обработчиком.

Вы обязательно хотите System.Windows.Forms.Timer здесь.Вы не выполняете никакой полезной работы с потоком пула потоков.

2 голосов
/ 31 января 2011

Я не уверен, но я полагаю, что Таймер будет внутренне использовать Invoke или BeginInvoke также в свойстве SynchronizingObject.

Допустим, это свойство просто дает некоторую абстракцию разработчику; чтобы облегчить ему жизнь.

Мое предположение было действительно правильным, это то, что Reflector говорит нам о MyTimerCallback методе закрытого члена System.Timers.Timer:

 ElapsedEventHandler onIntervalElapsed = this.onIntervalElapsed;
 if (onIntervalElapsed != null)
 {
     if ((this.SynchronizingObject != null) && this.SynchronizingObject.InvokeRequired)
     {
         this.SynchronizingObject.BeginInvoke(onIntervalElapsed, new object[] { this, e });
     }
     else
     {
         onIntervalElapsed(this, e);
     }
 }
1 голос
/ 31 января 2011

Почему бы вам не использовать Таймер WinForms ? Это основано на сообщениях окна и всегда будет выполняться в потоке пользовательского интерфейса; так как вы хотите выполнять обновления, когда поток пользовательского интерфейса должен все равно перекачивать сообщения, это может быть лучшим решением (синхронизация / блокировка не требуется).

0 голосов
/ 16 ноября 2017

Дело в том, что СФЕРА кода.Весь обработчик событий?или просто код изменения пользовательского интерфейса.

timer.SynchronizingObject заставляет обработчик событий вызываться в потоке данного объекта.Если вы установите это в this в классе Form1, это означает, что весь ваш код для обработки события таймера выполняется тем же потоком экземпляра Form1, даже если вы создали таймер на основе потока.Так что это НИЧЕГО.ЖЕ, как окна формы таймера.

Причина, по которой вы хотите это сделать, заключается в том, что вы хотите получить доступ к элементам управления пользовательского интерфейса и внести некоторые изменения в Form1, но весь пользовательский интерфейс зависает, если вы используете таймер формы Windows.Поскольку все они выполняются в одном потоке.

Чтобы избежать этого, вы используете основанный на потоках таймер.Ваш обработчик событий вызывается в другом потоке, поступающем из некоторого пула системных потоков.Это решает проблему зависания пользовательского интерфейса.Однако это поднимает еще одну проблему.Межпоточный доступ к пользовательскому интерфейсу контролирует исключение.В некоторых версиях Visual Studio вы можете отключить эту проверку в отладочных сборках.Но вы никак не можете просто обойти эту проверку при сборке релизов.Эта проверка потока предназначена для защиты от сбоев, вызванных многопоточностью.ТОГДА вам нужно использовать все эти вещи для вызова и делегирования.

Важной частью здесь является то, что это всего лишь несколько строк кода, которые перенаправляются для запуска в потоке Form1.Не весь обработчик событий.Большая часть кода обработки событий выполняется в другом потоке.И это включает в себя такой код, как выполнение каких-либо действий в сети или на диске.

Это делает РАЗНИЦУ.

НО, все это применимо только к приложениям формы Windows.Для WPF просто используйте диспетчерский таймер.(Вот почему вы не можете найти Windows.Threading в WinForm, потому что вы не можете использовать диспетчерский таймер в WinForm, но доступны в WPF)

0 голосов
/ 31 января 2011

Решения с таймером, подобные этому, на самом деле немного хакерские.

Вам лучше написать правильный асинхронный поток и выполнить обратный вызов либо с BeginInvoke, либо с SynchronizationContext .

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...