Я использую несколько System.Threading.Timer(s) в своем многопоточном приложении (на самом деле три таких таймера), но, несмотря на то, что он работает очень хорошо, у меня такое чувство, что я делаю это неправильно и потребляю ненужные ресурсы. Практически я делаю вот что:

  1. В главном окне своего приложения (окно WPF, но не думайте, что это так важно) я открываю три потока.

  2. в каждом из этих трех потоков я инициализирую и запускаю один из трех таймеров

Причина, по которой я использую для этого три разных потока, заключается в том, что я помню, что где-то читал (не могу найти где), что, когда я запускаю System.Threading.Timer, я больше не могу использовать текущий поток (например, поток каким-то образом остановился бы при запуске таймера).

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

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    System.Threading.Timer _t1, _t2, _t3;

    private void Form1_Load(object sender, EventArgs e)
    {
        _t1 = new System.Threading.Timer(new TimerCallback(_t1Run),
                           null, TimeSpan.Zero, TimeSpan.FromMilliseconds(15));
        _t2 = new System.Threading.Timer(new TimerCallback(_t2Run),
               null, TimeSpan.Zero, TimeSpan.FromMilliseconds(35));
        _t3 = new System.Threading.Timer(new TimerCallback(_t3Run),
               null, TimeSpan.Zero, TimeSpan.FromMilliseconds(150));

        for (int i = 1; i <= 5000; i++)
        {
            Console.WriteLine("Writting on main form " + i);
        }
    }

    void _t1Run(object State)
    {
        _t1.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
        Console.WriteLine("running 1");
        _t1.Change(TimeSpan.FromMilliseconds(15), TimeSpan.FromMilliseconds(15));
    }

    void _t2Run(object State)
    {
        _t2.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
        Console.WriteLine("running 2");
        _t2.Change(TimeSpan.FromMilliseconds(35), TimeSpan.FromMilliseconds(35));
    }

    void _t3Run(object State)
    {
        _t3.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
        Console.WriteLine("running 3");
        _t3.Change(TimeSpan.FromMilliseconds(150), TimeSpan.FromMilliseconds(150));
    }

}

Итак, могу ли я просто запустить все три таймера в главном окне (или мне нужны отдельные потоки)? Может ли кто-нибудь подтвердить, что это не вызывает проблем (потому что в приложении, которое я тестировал, проблем не возникает)?

Изменить : Три таймера имеют разные цели, и два из них на самом деле имеют некоторое взаимодействие с пользовательским интерфейсом (один взаимодействует с главным окном, а другой - с дополнительным окном - третий выполняет только некоторые фоновые операции), но причина, по которой я не использую System.Windows.Forms.Timer заключается в том, что таймеры выполняют множество операций, и если бы я выполнял все эти операции в основном потоке, я бы практически заблокировал все взаимодействия с графическим интерфейсом пользователя (я вызываю эти потоки-таймеры с интервалом в 100-200 миллисекунд. , так что только представьте ...). Это пример того, как я выполняю взаимодействия с графическим интерфейсом пользователя из потоков, вызываемых таймерами:

MainWindow.ThisInstance.TitleLabel.Dispatcher.BeginInvoke((Action)(() => UpdateMainWindow_TimeLabel(TimeLabelValue)));
0
TheQuestioner 4 Авг 2013 в 00:30

1 ответ

Лучший ответ

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

System.Threading.Timer раскручивает поток пула потоков всякий раз, когда истекает промежуток времени. Но это может создать проблемы с элементами формы, поскольку они не являются потокобезопасными. Доступ к элементам пользовательского интерфейса с использованием потоков, отличных от выделенного потока пользовательского интерфейса, может привести к нежелательному результату.

Чтобы преодолеть небезопасное поведение этого потока, следует использовать System.Windows.Forms.Timer. System.Windows.Forms.Timer не создает новый поток и не запускает новый поток пула потоков. Он просто запускает обратный вызов таймера в потоке пользовательского интерфейса. Таким образом, все взаимодействия с пользовательским интерфейсом выполняются в одном потоке, что обеспечивает безопасность потоков.

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

4
nhrobin 4 Авг 2013 в 00:54
Лучше дать пояснение в своем ответе. Действительно, эти таймеры существуют для разных целей.
 – 
Dennis
4 Авг 2013 в 00:35
@nhrobin, ну, вот следующий трюк, он говорит мне, что метка обновляется в потоке графического интерфейса: MainWindow.ThisInstance.TitleLabel.Dispatcher.BeginInvoke((Action)(() => UpdateMainWindow_TimeLabel(TimeLabelValue))); private static void UpdateMainWindow_TimeLabel(String TimeLabelValue) { Console.WriteLine("Name of thread running this: " + Thread.CurrentThread.Name); MainWindowInstance.TimeLabel.Content = TimeLabelValue; }
 – 
TheQuestioner
4 Авг 2013 в 01:00
@nhrobin, System.ComponentModel.BackgroundWorker также кажется жизнеспособным решением. Просто сначала хочу лучше понять, почему мое решение не (так) хорошо, учитывая тот факт, что Dispatcher.BeginInvoke (), похоже, делает трюк (и, по-видимому, выполняет все итерации в потоке графического интерфейса).
 – 
TheQuestioner
4 Авг 2013 в 01:16
1
Ваше решение в порядке. Есть только некоторые проблемы с читабельностью. Как видите, вам нужно написать гораздо более длинный оператор для обновления простой метки MainWindow.ThisInstance.TitleLabel.Dispatcher.BeginInvoke ((Action) (() => UpdateMainWindow_TimeLabel (TimeLabelValue)));
 – 
nhrobin
4 Авг 2013 в 01:23
1
Для сложной задачи вы можете использовать BackgroundWorker. Но для более простых, я думаю, вы можете придерживаться своей текущей реализации, поскольку реализация BackgroundWorker скорее внесет больше кодов / сложности.
 – 
nhrobin
4 Авг 2013 в 02:11