Форум программистов, компьютерный форум, киберфорум
C# Windows Forms
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  
 
 
Рейтинг 4.83/6: Рейтинг темы: голосов - 6, средняя оценка - 4.83
0 / 0 / 0
Регистрация: 23.01.2022
Сообщений: 25

Почему событие по таймеру запускается несколько раз?

05.05.2023, 16:10. Показов 1586. Ответов 25
Метки нет (Все метки)

Господа, помогите разобраться. Почему выбранные процессы запускаются по несколько экземпляров (когда 2, когда 3, когда 5)https://github.com/Iov1223/TaskManager - вот сам проект, на всякий случай
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
using System;
using System.IO;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Windows.Forms;
using System.Data.SQLite;
using static System.Data.Entity.Infrastructure.Design.Executor;
 
 
namespace TaskManager
{
    public partial class Form1 : Form
    {
        private string path;
        private string appName;
        private FileVersionInfo fileInfo;
        private static string nameDB = "schedulerDB.sqlite";
        private SQLiteConnection connection = new SQLiteConnection($"Data Source={nameDB};Version=3;");
        private SQLiteCommand command = new SQLiteCommand();
        private List<string> task = new List<string>();
        private List<string> time = new List<string>();
 
        public Form1()
        {
            InitializeComponent();
            timer1.Interval = 1000;
            timer1.Enabled = false;
            timer1.Tick += timer1_Tick;
            dataGridView1.ReadOnly = true;
        }
 
        private void buttonSchedule_Click(object sender, EventArgs e)
        {
            if (dateTimePicker1.Value < DateTime.Now)
            {
                MessageBox.Show("Невозможно запланировать на прошедшее время!");
            }
            else
            {
                timer1.Enabled = true;
                if (path != null)
                {
                    try
                    {
                        command.CommandText = "INSERT INTO Tasks" +
                                "(path_task, name_task, time, status)" +
                                "VALUES" +
                                $"('{path}', '{appName}', '{dateTimePicker1.Value.ToString("dd.MM.yyyy HH:mm:ss")}', 'Ожидает');";
                        command.ExecuteNonQuery();
                        task.Add(path);
                        time.Add(dateTimePicker1.Value.ToString("dd.MM.yyyy HH:mm:ss"));
                        fill_table();
                        command = new SQLiteCommand("SELECT * FROM Tasks", connection);
                        SQLiteDataReader reader = command.ExecuteReader();
                        {
                            while (reader.Read())
                            {
                                object _id = reader.GetValue(0);
                                object _path_task = reader.GetValue(1);
                                object _name_task = reader.GetValue(2);
                                object _time = reader.GetValue(3);
                                object _status = reader.GetValue(4);
                                time.Add($"{_time}");
                                task.Add($"{_path_task}");
                            }
                        }
                        reader.Close();
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show("Не Добавлено!" + "\n" + ex.Message);
                    }
                }
                else
                {
                    MessageBox.Show("Файл не выбран");
                }
            }
        }
 
        private void buttonSelection_Click(object sender, EventArgs e)
        {
            
            OpenFileDialog openFileDialog1 = new OpenFileDialog();
 
            openFileDialog1.InitialDirectory = "C:\\";
            openFileDialog1.Filter = "Text files (*.exe)|*.exe";
            openFileDialog1.FilterIndex = 2;
            openFileDialog1.RestoreDirectory = true;
            
            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                path = openFileDialog1.FileName;
                fileInfo = FileVersionInfo.GetVersionInfo(path);
                appName = fileInfo.FileDescription;
                MessageBox.Show("Выбран: " + appName);
            }
            else
            {
                MessageBox.Show("Файл не выбран.");
            }
 
        }
 
        private void timer1_Tick(object sender, EventArgs e)
        {
            for (int i = 0; i < task.Count; i++)
            {
                if (time[i] == DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss"))
                {
                    Process.Start(task[i]);
                    try
                    {
                        command.CommandText = $"UPDATE Tasks SET status='Выполнено' WHERE time='{time[i]}';";
                        command.ExecuteNonQuery();
                        fill_table();
                        dataGridView1.Refresh();
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                    }
                }
                else if (DateTime.Parse(time[i]) < DateTime.Now)
                {
                    command.CommandText = $"UPDATE Tasks SET status='Неудачно' WHERE status='Ожидает';";
                    command.ExecuteNonQuery();
                    fill_table();
                    dataGridView1.Refresh();
                }
            }
        }
 
        private void Form1_Load(object sender, EventArgs e)
        {
            if (!File.Exists(nameDB))
            {
                SQLiteConnection.CreateFile(nameDB);
            }
 
            try
            {
                connection.Open();
                this.Text = "Планировщик. Ссоединение с базой данных: УСТАНОВЛЕННО";
                command.Connection = connection;
                command.CommandText = "CREATE TABLE IF NOT EXISTS Tasks" +
                    "(" +
                        "id INTEGER PRIMARY KEY AUTOINCREMENT," +
                        "path_task VARCHAR (100) NOT NULL," +
                        "name_task VARCHAR (100) NOT NULL," +
                        "time VARCHAR (20) NOT NULL," +
                        "status VARCHAR (100) NOT NULL" +
                     ")";
                command.ExecuteNonQuery();
                fill_table();
            }
            catch (Exception ex)
            {
                this.Text = "Планировщик. Ссоединение с базой данных: НЕ УСТАНОВЛЕННО";
                MessageBox.Show("Не удалось подключится к базе данных!" + "\n" + ex.Message);
            }
        }
        private void fill_table()
        {
            SQLiteDataAdapter adapter = new SQLiteDataAdapter("SELECT * FROM Tasks", connection);
            DataTable table = new DataTable();
            adapter.Fill(table);
            dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
            dataGridView1.DataSource = table;
            dataGridView1.FirstDisplayedScrollingRowIndex = dataGridView1.Rows.Count - 1;
        }
 
        private void buttonClear_Click(object sender, EventArgs e)
        {
            command.CommandText = "DELETE FROM Tasks";
            command.ExecuteNonQuery();
            fill_table();
            task.Clear();
            time.Clear();
            timer1.Enabled = false;
            dataGridView1.Refresh();    
        }
    }
}
0
Лучшие ответы (1)
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
05.05.2023, 16:10
Ответы с готовыми решениями:

Почему событие срабатывает несколько раз
почему в веббраузере за время загрузки страницы событие документкомплейт срабатывает несколько раз??? как сделать что бы срабатовалo один...

Почему при нажатии кнопки при использовании библиотеки createjs функция запускается несколько раз?
Здравствуйте, на creatjs делаю переключения между уровнями. Когда нажимаю на кнопку то уровень очищается и появляются новые объекты, щелкаю...

Событие вызывается несколько раз
Второй день, как стал изучать JavaScript. Пишу функцию, которая формирует список папок и файлов из заранее полученных данных. на...

25
Эксперт .NET
 Аватар для Rius
13324 / 7779 / 1690
Регистрация: 25.05.2015
Сообщений: 23,697
Записей в блоге: 14
07.05.2023, 12:54
iov1223, да, в запланированное. Но с таймерами проблема в том, что точно в запланированное время они сработать не могут. Вы вызываете его раз в секунду. Поэтому придётся смотреть, была ли запущена задача с прошлого тика таймера, если между прошлым тиком таймера и текущим находится то самое время, когда должна быть запущена задача.
А чтобы не запускать дважды, надо у задачи завести флаг "запущена", ставить его при запуске и не запускать, если флаг уже установлен.
1
0 / 0 / 0
Регистрация: 23.01.2022
Сообщений: 25
07.05.2023, 13:31  [ТС]
Rius, Спасибо, буду разбираться, если что вернусь обратно, и опять буду доставить сдешних обитателей вопросами. Но это будет не скоро. Ещё раз всем спасибо
0
Эксперт .NET
 Аватар для Wolfdp
3790 / 1767 / 371
Регистрация: 15.06.2012
Сообщений: 6,543
Записей в блоге: 3
07.05.2023, 14:02
Цитата Сообщение от iov1223 Посмотреть сообщение
а лишь сокращает запускаемые экземпляры (не 4, а 2)
У меня подозрение что у вас подписка на Tick как в конструкторе, так и в Form1.Designer.cs. В гит-коде вроде нет, но черт его знает что вы там ещё наворотили
0
0 / 0 / 0
Регистрация: 23.01.2022
Сообщений: 25
07.05.2023, 14:38  [ТС]
Wolfdp, спасибо. Обязательно проверю, как только доберусь.
0
Эксперт .NET
 Аватар для Wolfdp
3790 / 1767 / 371
Регистрация: 15.06.2012
Сообщений: 6,543
Записей в блоге: 3
08.05.2023, 10:49
Лучший ответ Сообщение было отмечено iov1223 как решение

Решение

Скину будущим "ищу готовое" небольшой рабочий пример.

ТехЗадание

-- форма с гридом задач. В столбцах отображаем "имя приложения" (берется из свойств exe файла), "полный путь к файлу", "ожидаемое время запуска" и "статус"
-- статус может быть "ожидает", "запущено", "ошибка", "пропущено"
-- внизу формы контролы для выбора файла на запуск (текстбокс с путем к файлу и датапикер для указания времени запуска)
-- также есть кнопка очистить все задачи
-- при наличии задач со статусом "ожидает", нужно каждую секунду проверять время запуска, и если "да" -- запускаем приложение. Если задач на запуск нет, таймер должен быть остановлен
-- если при проверке обнаруживается что задача должна была быть запущена более 10 секунд тому, то считаем что время запуска прошло, и выставляем "пропущено" без запуска программы. Если при запуске вывалило с ошибкой -- проставляем статус "ошибка".
-- всю информацию про задачи нужно дополнительно сохранять в базу данных sqlite
-- дополнительно есть кнопка "очистить", которая удаляет все задачи (включая ожидающие) и из списка и БД


1. Есть явных два модуля: сам таймер, работающий со списком ожидающих задач и модуль для работы с БД.
примечание
Вообще нужно всегда стараться логические блоки выносить в отдельные части, и БД тут не единственный пример. Грубо говоря ваши внутренности должны быть легко переносимы с WinForms на Console. В идеале конечно код должен быть вообще под Unit Test.

2. Из моделей явно есть объект описывающий задачу (путь, статус, время). Для работы с БД, нужно также добавить Id (будем использовать Guid). Также явно нужен enum описывающий статусы.
примечание
Отмечу, что в примере ТСа дата, статус -- строки. Так поступать не стоит. Во-первых такой подход череват ошибками из-за кодировки или банально несовпадения регистра. Во-вторых перегоняя в строки, мы совершаем дополнительную работу (код работает медленнее, хоть и не критично, особенно в нашей задаче)

3. Форма. Тут ничего особенного, разве что для Grid добавим отдельную форму для отображения данных (без нормального DataBinding, но чуть упростит вывод данных)

Модель данных
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ScheduleTask
{
    public Guid Id { get; set; }
    public string Path { get; set; } = default!;
    public string AppName { get; set; } = default!;
    public ScheduleTaskStatus Status { get; set; }
    public DateTime Time { get; set; }
 
//метод для создания таски на основе входящих данных от пользователя
    public static ScheduleTask Create(string path, string appName, DateTime time)
    { 
        return new ScheduleTask
        {
            Id = Guid.NewGuid(),
            AppName = appName,
            Time = time,
            Path = path,
            Status = ScheduleTaskStatus.Waiting
        };
    }
}
Модуль управления задачами (см. файл ScheduleTaskManager.cs)
- используем таймер из области имен System.Timers. Виндовый таймер нам не подходит, так как "переносимость" и он не предназначен для фоновых задач, а больше для взаимодействия с UI.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ScheduleTaskManager : IDisposable
{
    private readonly System.Timers.Timer _timer;
 
    public ScheduleTaskManager()
    {
        _timer = new(TimeSpan.FromSeconds(1))
        _timer.Elapsed += Tick;
    }
 
    private void Tick(object? sender, ElapsedEventArgs e)
    {
       //проверка необходимости что-либо запустить
    }
}
- в виду того что при добавлении новой задачи, может происходит обход списка, мы можем нарваться на ошибку "список был изменен". Чтобы этого не происходило, будем использовать блокировку ReaderWriterLock (ниже не весь код, где он применяется, просто на что ориентироваться)
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    private readonly ReaderWriterLock _locker;
    private readonly List<ScheduleTask> _tasks;
 
    public void Add(ScheduleTask task)
    {
        _locker.AcquireWriterLock(LockerDelay);
        try
        {
            _tasks.Add(task);
            if (task.Status == ScheduleTaskStatus.Waiting)
                _timer.Start();
        }
        finally
        {
            _locker.ReleaseWriterLock();
        }
    }
- так как отработка прохода списка может происходить дольше 1 секунды, будем использовать флаг tickRunning, чтобы в случае занятости пропускать текущий вызов
C#
1
2
3
4
5
    private void Tick(object? sender, ElapsedEventArgs e)
    {
        if (tickRunning)
            return;
        tickRunning = true;
- после запуска/ошибки задачи, выкидываем её из внутреннего списка
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
            if (haveProcessed)
            {
                var lc = _locker.UpgradeToWriterLock(LockerDelay);
                try
                {
                    var processed = _tasks.Where(t => t.Status != ScheduleTaskStatus.Waiting)
                                          .ToArray();
                    _tasks.RemoveAll(t => processed.Contains(t));
                    if (_tasks.Count == 0)
                        _timer.Stop();
                    TasksProcessed?.Invoke(processed);
                }
                finally
                { 
                    _locker.DowngradeFromWriterLock(ref lc);
                }
            }
- если список пустой -- останавливаем таймер, при добавлении -- стартуем
- весь модуль сделаем как IDisposable, чтобы можно было быстро грохнуть внутренний таймер
- добавляем событие TasksProcessed, чтобы информировать стороние модули, что произошел запуск/ошибка задачи.

Модуль БД (файл DbRepository.cs)

примечание
Вообще по нормальному, нужна ORM, тот же EF хотя бы... Но раз ТС изначально писал на чистом ADO -- будем использовать его, с некоторыми коректировками.
1. методы Execute для унификации вызова (не то чтобы тут много, но обычно это первое что приходиться велосипедить)
2. введем константы наименования таблиц и колонок. Делается это для того, чтобы сократить вероятность опечаток. SQL в коде в принципе зло (поэтому и исползуют ORM), так как различные ошибки, например банальная опечатка"select * from tassk" обнаружиться только при вызове, а не компиляции.
3. Работаем исключительно через параметры. Никаких ToString в тело sql-запроса, это не безопасно.

Просто реализуем необходимые методы:
- инициализация БД, если её ещё нет (см. InitDB)
- добавления новой таски (AddTask). Никаких проверок, вся валидация -- не уровень ответсвенности БД.
- загрузить все имеющиеся таски (LoadTasks), т.к. грузим как для grid все, так и только для модуля с таймером, добавим вариант с флагом "грузить только таски ожидающие запуска". Хотя это можно делать и на уровне выше, а тут просто отдавать всё что есть.
- обновить статус таски (UpdateTaskStatus)
- очистить базу (Clear)

Форма

Добавляем модель для отображения (не то чтобы она тут прям сильно нужна, но так кошернее)
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ScheduleTaskView
{
    private static readonly IReadOnlyDictionary<ScheduleTaskStatus, string> _displayStatuses
        = new Dictionary<ScheduleTaskStatus, string>()
    {
        { ScheduleTaskStatus.Missed, "The task was skipped." },
        { ScheduleTaskStatus.Failed, "Failed!!!" },
        { ScheduleTaskStatus.Waiting, "Waiting..." },
        { ScheduleTaskStatus.Started, "Completed." }
    };
    public string Path { get; set; }
    public string AppName { get; set; }
    public DateTime Time { get; set; }
    public string Status { get; set; }
 
    public ScheduleTaskView(ScheduleTask task)
    {
        Path = task.Path;
        AppName = task.AppName;
        Time = task.Time;
        Status = _displayStatuses[task.Status];
    }
}
Чтобы не мучиться с Table, мы привязываем grid к списку тасок, а в настройках колонок задаем для DataPropertyName соответсвующее поле из ScheduleTaskView. Т.к. нам нужно обновлять отображение на старте + добавление + очистка + TasksProcessed -- добавляем метод для этого дела. Не забываем что вызов обновления грида может произойти не только из главного потока (таймер срабатывает в пуле)
C#
1
2
3
4
5
6
private void RefreshGrid()
    {
        var tasks = _dbRepository.LoadTasks();
        var viewTasks = tasks.Select(t => new ScheduleTaskView(t)).ToArray();
        BeginInvoke(() => taskDataGridView.DataSource = viewTasks);
    }
В целом форма выступает в роли агрегатора для первых двух модулей (по хорошему тут нужен отдельынй агрегатор, который уже будет взаимодействовать с формой).
Вложения
Тип файла: zip Nya.TaskManager.zip (14.5 Кб, 13 просмотров)
1
0 / 0 / 0
Регистрация: 23.01.2022
Сообщений: 25
08.05.2023, 15:06  [ТС]
Wolfdp, спасибо огромное, вернусь из отпуска, буду разбирать, вникать, мотать на ус
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
08.05.2023, 15:06

Почему если несколько раз вызвать функцию она сработает только один раз
#include &lt;iostream&gt; using namespace std; int main() { int func(); { cout &lt;&lt; &quot;AAA&quot;; return 1; ...

событие onclick вызывается несколько раз
Всем привет, У меня есть 40 файлов и при каждой прокрутке вызывается 10 файлов. Я нажимаю на файл и выводится alert(1); но при...

Событие клика отрабатывает несколько раз
Всем привет, создаю динамично элементы(в примере решил сделать статичными) после создания добавляю для каждого из них событие клика, но...

Событие ввода текста срабатывает несколько раз
Есть форма: &lt;form id=&quot;main_form&quot;&gt; &lt;p&gt;Регистрация&lt;/p&gt; &lt;input name=&quot;login&quot;...

Событие MouseWheel в PictureBox срабатывает несколько раз
Событие MouseWheel в PictureBox срабатывает несколько раз. Сначала 1, затем 2, 4, 8 и так далее. Помогите решить проблему. Колесо...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
26
Ответ Создать тему
Новые блоги и статьи
Свет внутри себя
kumehtar 07.06.2026
Пусть это будет здесь lIs4oanZS9Y
Программа для com-порта
Uhbif79 05.06.2026
Всем привет, давно хотел изучить Qt, начинал, бросал, потом снова начинал. И сейчас вот смог написать свою первую программу. До этого имел опыт программирования микроконтроллеров, писал прошивки на. . .
Транскрипция 55-минутного видео через Whisper: WhisperDesktop облажался, спас Google Colab[
anaschu 01.06.2026
Понадобилось получить текст из свежезагруженного видео на YouTube. Казалось бы, задача на пять минут. Заняла полтора часа. Делюсь опытом — может кому пригодится последовательность решений. . . .
21 мат мед. Планы на развитие модели здравоСохранения
anaschu 01.06.2026
AnyLogic: план развития симуляционной модели рабочего коллектива — динамический абсентеизм, реальные данные, три сценария сравнения Продолжаю серию постов о дискретно-событийной модели рабочего. . .
20. Мат мед. Абсентеизм как отдельный тип простоя
anaschu 29.05.2026
Апдейт модели: исправленные баги, абсентеизм и новые механизмы Продолжаю развивать ранее описанную модель рабочего коллектива на AnyLogic. За последние несколько дней был проведён серьёзный. . .
19. здоровье, усталость и психотип работника влияют на производительность предприятия, и наоборот, производительность на здоровье, усталось и психотип
anaschu 28.05.2026
Дискретно-событийная модель рабочего коллектива на AnyLogic: здоровье, выгорание, психотипы и микростимуляция Привет, коллеги. Хочу поделиться итогами нескольких недель работы над симуляционной. . .
"Прокси" для последовательного порта
Eddy_Em 28.05.2026
Эту штуку написал я достаточно давно. Но сейчас вот понадобилось настроить датчик грозы, но при этом не отключать его от "метеодемона". Соответственно, надо запустить этот "прокси": метеодемон будет. . .
Рефакторинг программы уравнивания.
Massaraksh7 26.05.2026
Пример по предыдущей записи в блоге. Но, надо заметить, что, во-первых, там оптимизация не только математики, но и работы с базой данных, и с графами, а во-вторых, это ещё не всё.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru