Форум программистов, компьютерный форум, киберфорум
Python: Tkinter
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.69/13: Рейтинг темы: голосов - 13, средняя оценка - 4.69
0 / 0 / 0
Регистрация: 09.05.2022
Сообщений: 7

Tkinter + Threading: Tcl_AsyncDelete: async handler deleted by the wrong thread

09.05.2022, 23:19. Показов 3242. Ответов 12

Студворк — интернет-сервис помощи студентам
Всем привет. Коллеги, помогите решить проблему Tcl_AsyncDelete: async handler deleted by the wrong thread.
Описание программы: Занимается парсингом футбольных матчей, даёт возможность поставить матчи очередь проверки, и отдельный класс бегает проверяет очередь на то подошло ли время, если подошло - делает запрос на сайт для получения инфы, проверяет по логике и если всё ок - отправляет в группу телеграмма. Приложил ниже пример одного из окон и вкратце запуск приложения + чекер
Окна открываются через
Python
1
2
3
def history(self):
  window = history_win()
  window.grab_set()
Python
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
class sender():
  def __init__(self,arg):
    self.session = requests.Session()
    t = threading.current_thread()
    while getattr(t, "do_run", True):
        print ("working on %s" % arg)
        time = db.get_start_time()[0]
        delta = int(round(datetime.today().timestamp()))
        if delta-time>=3600:
          logging.info(f'[Sender] Обновляю сессию')
          self.session = requests.Session()
          time = int(round(datetime.today().timestamp()))
          db.upd_start_time(time)
        self.parsing()
        sleep(20)
  def parsing(self):
    Творим магию с запросами к базе/сайтам
  def send_telegram(self,config):
    Собираем текст и отправляем в телеграмм
 
class history_win(Tk):
    def __init__(self):
        super().__init__()
        self.title('История отправленных матчей')
        self.iconbitmap('files/SFlogo.ico')
        width_of_window = 1770
        height_of_window = 850
        screen_width = self.winfo_screenwidth()
        screen_height = self.winfo_screenheight()
        x_coordinate = (screen_width/2)-(width_of_window/2)
        y_coordinate = (screen_height/2)-(height_of_window/2)
        self.geometry("%dx%d+%d+%d" %(width_of_window,height_of_window,x_coordinate,y_coordinate))
 
        Frame(self,width=screen_width,height=screen_height,bg=app_color).place(x=0,y=0)  #249794
 
        self.style = ttk.Style()
        self.style.theme_use('default')
        self.style.configure('Treeview', background='#D3D3D3',
        foreground='black',rowheight=35,fieldbackground='#D3D3D3')
        self.style.map('Treeview',background=[('selected','#347083')])
        self.tree_frame = Frame(self)
        self.tree_frame.pack(expand=True, fill='y')
        self.tree_scroll = Scrollbar(self.tree_frame,orient='vertical')
        self.tree_scrollx = Scrollbar(self.tree_frame,orient='horizontal')
        self.tree_scroll.pack(side=RIGHT, fill=Y)
        self.tree_scrollx.pack(side='bottom', fill=X)
        self.my_tree = ttk.Treeview(self.tree_frame, yscrollcommand=self.tree_scroll.set, xscrollcommand=self.tree_scrollx.set, selectmode='extended')
        self.my_tree.pack()
        self.my_tree.config(height=screen_height)
        self.tree_scroll.config(command=self.my_tree.yview)
        self.tree_scrollx.config(command=self.my_tree.xview)
        self.my_tree['columns'] = ('League','Commands','Date','both_yes','both_no','score','goals','ind_total_1t','ind_total','exact_score_1t','exact_score_2t','exact_goals_1t','Total','outcomes','f_goal','Link')
        self.my_tree.column('#0', width=0, stretch=NO)
        self.my_tree.column('League', width=275, anchor=CENTER)
        self.my_tree.column('Commands', width=275, anchor=CENTER)
        self.my_tree.column('Date', width=125, anchor=CENTER)
        self.my_tree.column('both_yes', width=50, anchor=CENTER)
        self.my_tree.column('both_no', width=50, anchor=CENTER)
        self.my_tree.column('score', width=125, anchor=CENTER)
        self.my_tree.column('goals', width=100, anchor=CENTER)
        self.my_tree.column('ind_total_1t', width=100, anchor=CENTER)
        self.my_tree.column('ind_total', width=100, anchor=CENTER)
        self.my_tree.column('exact_score_1t', anchor=CENTER)
        self.my_tree.column('exact_score_2t', width=100, anchor=CENTER)
        self.my_tree.column('exact_goals_1t', width=100, anchor=CENTER)
        self.my_tree.column('Total', width=100, anchor=CENTER)
        self.my_tree.column('outcomes', width=100, anchor=CENTER)
        self.my_tree.column('f_goal', width=100, anchor=CENTER)
        self.my_tree.column('Link', width=275, anchor=CENTER)
        self.my_tree.heading('#0',text='', anchor=W)
        self.my_tree.heading('League',text='Лига', anchor=CENTER)
        self.my_tree.heading('Commands',text='Команды', anchor=CENTER)
        self.my_tree.heading('Date',text='Дата', anchor=CENTER)
        self.my_tree.heading('both_yes',text='ОЗ_Да', anchor=CENTER)
        self.my_tree.heading('both_no',text='ОЗ_Нет', anchor=CENTER)
        self.my_tree.heading('score',text='Счет', anchor=CENTER)
        self.my_tree.heading('goals',text='Голы', anchor=CENTER)
        self.my_tree.heading('ind_total_1t',text='Инд. тотал 1-й тайм', anchor=CENTER)
        self.my_tree.heading('ind_total',text='Индивидуальный тотал', anchor=CENTER)
        self.my_tree.heading('exact_score_1t',text='Точный счет 1-го тайма', anchor=CENTER)
        self.my_tree.heading('exact_score_2t',text='Точный счет 2-го тайма', anchor=CENTER)
        self.my_tree.heading('exact_goals_1t',text='Точное кол-во голов', anchor=CENTER)
        self.my_tree.heading('Total',text='Тотал', anchor=CENTER)
        self.my_tree.heading('outcomes',text='Исходы по таймам', anchor=CENTER)
        self.my_tree.heading('f_goal',text='Первый гол', anchor=CENTER)
        self.my_tree.heading('Link',text='Ссылка', anchor=CENTER)
 
        self.my_tree.tag_configure('oddrow', background='white')
        self.my_tree.tag_configure('evenrow', background='lightblue')
 
        self.data = db.get_history()
 
        for record in enumerate(self.data):
            if record[0] % 2 == 0:
              item = self.my_tree.insert(parent='', index='end', iid=record[0], text='', values=(record[1][0], record[1][1], record[1][2], record[1][3], record[1][4], record[1][5], record[1][6], record[1][7], record[1][8], record[1][9],record[1][10],record[1][11],record[1][12],record[1][13],record[1][14],record[1][15]))
              self.my_tree.item(item,tags=('evenrow',))
            else:
              item = self.my_tree.insert(parent='', index='end', iid=record[0], text='', values=(record[1][0], record[1][1], record[1][2], record[1][3], record[1][4], record[1][5], record[1][6], record[1][7], record[1][8], record[1][9],record[1][10],record[1][11],record[1][12],record[1][13],record[1][14],record[1][15]))
              self.my_tree.item(item,tags=('oddrow',))
 
 
if __name__ == "__main__":
    t = threading.Thread(target=sender,args=('task',), daemon=True)
    t.start()
    app = App()
    app.mainloop()
    t.do_run = False
0
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
09.05.2022, 23:19
Ответы с готовыми решениями:

Threading.Thread и Dictionary(of)
Доброго времени суток! Ребята вот такая маленькая проблема! Весь Код скидывать не буду потому что много чего лишнего ! Попробую описать...

Multiprocessing и аналог threading.Thread
В библиотеке threading есть замечательный класс Thread, от которого можно наследовать и запускать прекрасные потоки. Начал дальше...

System::Threading::Thread Потоки в CLR
Можете подсказать как работает потоки в CLR ? Обычные потоки (std::thread] не могу использовать можете прислать ссылки на статьи? Или сами...

12
 Аватар для kapbepucm
1568 / 741 / 321
Регистрация: 02.05.2020
Сообщений: 1,660
10.05.2022, 10:54
Виджеты Tkinter не любят, когда к ним обращаются из других потоков. По преведённому коду мало, что можно сказать.

Добавлено через 5 минут
Приведите строку, которая даёт это ошибку, чтоли.
0
0 / 0 / 0
Регистрация: 09.05.2022
Сообщений: 7
10.05.2022, 16:28  [ТС]
Дело в том, что он не жалуется на определённую строку, но попробую отследить на какую именно тригерится.
Про виджеты я прочитал, поэтому не затрагиваю их из второго потока, там цикл бегает только по базе / запросам к сайту и к телеграмму.

Добавлено через 5 часов 15 минут
несколько раз уже завершило работу на строке
Python
1
json_ = json.loads(soup)
в контексте
Python
1
2
3
r = self.session.get(f"сайт", headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0'})
soup = bs4(r.content, 'lxml').text
json_ = json.loads(soup)
Но у меня вообще не клеится в голове как именно здесь может быть проблема..
0
0 / 0 / 0
Регистрация: 09.05.2022
Сообщений: 7
11.05.2022, 00:22  [ТС]
В общем - да. Я сделал кучу флагов и срабатывает действительно на строке указанной выше. Причем как воспроизводится проблема:
Запускаешь программу, бегаешь по окнам - воспроизведется ошибка, когда условие перехода на данный участок кода будет успешно. После этого запускаешь программу снова, не бегая по окнам - всё работает стабильно, даже при попадании на данный участок. Откроешь / закроешь какие-либо окна - снова будет ошибка.
0
 Аватар для kapbepucm
1568 / 741 / 321
Регистрация: 02.05.2020
Сообщений: 1,660
11.05.2022, 09:17
Цитата Сообщение от Xerber Посмотреть сообщение
цикл бегает только по базе / запросам к сайту и к телеграмму.
цикл бегает и никак вообще не взамодействует с gui? А кто таблицы заполняет/обновляет?
0
0 / 0 / 0
Регистрация: 09.05.2022
Сообщений: 7
11.05.2022, 11:12  [ТС]
Абсолютно никак.
Заполнение/Обновление таблиц на gui идёт в момент вызова самого окна с таблицей
0
 Аватар для kapbepucm
1568 / 741 / 321
Регистрация: 02.05.2020
Сообщений: 1,660
11.05.2022, 12:04
А попробуйте создавать и запускать новый поток после app = App(), а не до.
0
0 / 0 / 0
Регистрация: 09.05.2022
Сообщений: 7
12.05.2022, 17:06  [ТС]
Попробовал - проблема так же остаётся.
Проверил что ошибка воспроизводится только если ты открывал/закрывал другие окна.
Немного больше вводной инфы:
При первом запуске открывается окно с одной кнопкой (1й скрин), после нажатия идёт парсинг и открывает "разводящую страницу" с кнопками навигации по другим окнам.
Если с разводящей страницы не переходить никуда - всё отрабатывает корректно.
Если поставить вместо разводящей страницы открывать любое другое окно - всё работает корректно.
Но если с разводящей открыть другое окно (или открыть и закрыть его) - всё падает с ошибкой Tcl_AsyncDelete при подходе условий к выводу в телегу.
Разводящая открывается по коду:
Python
1
2
window = main_win()
self.destroy()
Пробовал заменить
Python
1
2
3
def history(self):
  window = history_win()
  window.grab_set()
на
Python
1
2
3
def history(self):
  window = history_win()
  self.destroy()
но успеха не принесло
Миниатюры
Tkinter + Threading: Tcl_AsyncDelete: async handler deleted by the wrong thread   Tkinter + Threading: Tcl_AsyncDelete: async handler deleted by the wrong thread  
0
290 / 205 / 68
Регистрация: 18.09.2019
Сообщений: 407
Записей в блоге: 58
12.05.2022, 19:39
Ну, Xerber, заинтриговали

Не хотите/ не можете показать свой код - нет проблем, давайте возьмём что-нибудь в качестве модельки и попробуем научить её работать правильно. Например, вот отсюда https://solveforum.com/forums/... ad.288388/ :
Python
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
from tkinter import *
from threading import Thread
 
loaded = False
 
def load():
 
    load = Tk()
 
    Label(load, text = "Loading...").pack()
 
    def check():
 
        if loaded:
 
            load.destroy()
 
        else:
 
            load.after(1, check)
 
    load.mainloop()
 
 
t = Thread(target = load)
t.start()
 
root = Tk()
 
#defining the 720 variables
for x in range(0, 720):
    print(x)
    exec("var" + str(x) + " = " + str(x))  
 
loaded = True
 
root.mainloop()
Тут тоже открываются 2 окна - стартовое tk (Loading...) и главное tk # 2.
Стартовое, правда, само не гаснет, но его можно и ручками закрыть.
А после этого можно закрыть основное и получить ту же самую ошибку, что и у Вас.

Что тут не так? Чего ей не хватает-то?
0
0 / 0 / 0
Регистрация: 09.05.2022
Сообщений: 7
12.05.2022, 21:03  [ТС]
Странно что есть функция check в load которая не вызывается, но мне и тут не понятно почему вылазит данная ошибка если мы закрываем окно из первого треда. Я так понимаю замысел был передать loaded в другой тред для закрытия окна, но она не глобальная даже..
Пытался связать со своим случаем - не получается т.к. мои потоки абсолютно не общаются друг с другом и не ожидают чего-то.
Вы правы, вот код треда
Python
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
class sender():
  def __init__(self,arg):
    self.session = requests.Session()
    t = threading.current_thread()
    while getattr(t, "do_run", True):
        #print ("working on %s" % arg)
        time = db.get_start_time()[0]
        delta = int(round(datetime.today().timestamp()))
        if delta-time>=3600:
          logging.info(f'[Sender] Обновляю сессию')
          self.session = requests.Session()
          time = int(round(datetime.today().timestamp()))
          db.upd_start_time(time)
        self.parsing()
        sleep(20)
 
  def parsing(self):
    r = db.get_config()
    db_config={'bot_token':r[0],'chat_id_1h':r[1],'chat_id_2h':r[2],'time_1h':int(r[3]),'time_2h':int(r[4]), 'scores':r[6]}
    q = db.get_queqe()
    if len(q)>0:
        logging.info(f"Вижу {len(q)} игр(ы) в очереди")
        for game in q:
            config={'league':game[0],'com':game[1],'date':game[2],'match':game[3],'half':game[4]}
            logging.info(f"[Sender] Смотрю {config['league']} {config['com']}")
            if config['half'] == 'half1':
                time = db_config['time_1h']
                config.update({'chat_id':db_config['chat_id_1h']})
            else:
                time = db_config['time_2h']
                config.update({'chat_id':db_config['chat_id_2h']})
            future = config['date']+timedelta(minutes=int(time))
            now = datetime.now()
            if now>=future:
                logging.info(f"[Sender] Время подходит. Сейчас {now}, Надо {future}")
                try:
                    r = self.session.get(f"https://ad.betcity.ru/d/on_air/bets?rev=8&add=dep_event&ids={config['match']}&stat=1&ver=271&csn=ooca9s", headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0'})
                except requests.exceptions.ConnectionError as e:
                    logging.warning(f"[Sender] {e}")
                    sleep(2)
                    continue
                soup = bs4(r.content, 'lxml').text
                json_ = json.loads(soup)
                try:
                    json_["reply"]["status"]
                    logging.info(f"[Sender] Матч не валиден")
                    db.del_match(config)
                    continue
                except KeyError:
                    pass
                try:
                    leagues = json_["reply"]["sports"]["1"]["chmps"]
                    logging.info(f"[Sender] взял лиги")
                    for league in leagues:
                        num = str(league) #ID события
                        events = leagues[num]["evts"]
                        for event in events:
                            cum = str(event)
                            match = events[cum]
                            id_ev = match["id_ev"]#Необходимый параметр для ссылки
                            cn_ch = match["cn_ch"]#Необходимый параметр для ссылки
                            sc_ev = match["sc_ev"]#Счет
                            if config['half'] == 'half2':
                                ext_n = f'{match["sc_ev_cmx"]["ext"][0][0]}:{match["sc_ev_cmx"]["ext"][0][1]}'
                            md_min = match["md_min"]#Время от которого считать
                            match_min = int(match["min"])#Время которое плюсуем
                            if config['half'] == 'half2':
                                match_time = int((datetime.now()-datetime.strptime(md_min,'%Y-%m-%d %H:%M:%S')).seconds//60)+45
                            else:
                                match_time = int((datetime.now()-datetime.strptime(md_min,'%Y-%m-%d %H:%M:%S')).seconds//60)+match_min#Высчитываем какая сейчас минута матча
                            link = f'https://betcity.ru/ru/live/soccer/{cn_ch}/{id_ev}'#Собираем ссылку для отправки в ТГ
                            config.update({'bot_token':db_config['bot_token'],'sc_ev':sc_ev,'time':match_time,'link':link})
                            if config['half'] == 'half2':
                                if config['time']>=db_config['time_2h']:
                                    if sc_ev == ext_n:
                                        if sc_ev in db_config['scores'].split(',') or db_config['scores'] == '':
                                            logging.info(f"[Sender][2H] Отправляю в ТГ")
                                            self.send_telegram(config)
                                        else:
                                            logging.info(f"[Sender][2H] Счет не в базе. Счет {sc_ev}, В базе {db_config['scores']}")
                                            db.del_match(config)
                                    else:
                                        logging.info(f"[Sender][2H] Гол уже был забит")
                                        db.del_match(config)
                                else:
                                    #pass
                                    logging.info(f"[Sender][2H] Время матча не подходит. Нужно {config['time']}, Сейчас {db_config['time_2h']}")
                            else:
                                if config['time']>=db_config['time_1h']:
                                    if sc_ev == '0:0':
                                        logging.info(f"[Sender][1H] Отправляю в ТГ")
                                        self.send_telegram(config)
                                    else:
                                        logging.info(f"[Sender][1H] Счет не подходит. Счет {sc_ev}")
                                        db.del_match(config)
                                else:
                                    #pass
                                    logging.info(f"[Sender][1H] Время матча не подходит. Нужно {config['time']}, Сейчас {db_config['time_1h']}")
                except KeyError as e:
                    pass
            else:
                #pass
                logging.info(f"[Sender] Время ещё не пришло. Сейчас {now}, Надо {future}")
        sleep(60)
    else:
      #pass
      logging.info(f"[Sender] Нет игр в очереди")
 
  def send_telegram(self,config):
    url = 'https://api.telegram.org/bot'
    url+=config['bot_token']
    method = url + "/sendMessage"
    text = f"������ {config['league']}\n������ {config['com']}"
    if config['half'] == 'half2':
        text+=f"\n������ {config['sc_ev']}"
    text+=f"\n⏱ {config['time']}\n{config['link']}"
    response = requests.post(method,data={"chat_id":config['chat_id'],"text":text,"disable_web_page_preview":'1'})
    logging.info(f'[Sender] {response.status_code=}')
    if response.status_code == 200:
        db.change_queue(config['league'],config['com'],config['date'],'Send')
Код первого окна
Python
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
class App(Tk):
    def __init__(self):
        super().__init__()
 
        db.clear_leagues()
 
        width_of_window = 427
        height_of_window = 250
        screen_width = self.winfo_screenwidth()
        screen_height = self.winfo_screenheight()
        x_coordinate = (screen_width/2)-(width_of_window/2)
        y_coordinate = (screen_height/2)-(height_of_window/2)
        self.geometry("%dx%d+%d+%d" %(width_of_window,height_of_window,x_coordinate,y_coordinate))
        self.overrideredirect(1)
 
 
        s = ttk.Style()
        s.theme_use('clam')
        s.configure("red.Horizontal.TProgressbar", foreground='red', background='#4f4f4f')
        self.progress=Progressbar(self,style="red.Horizontal.TProgressbar",orient=HORIZONTAL,length=437,mode='determinate')
 
        self.progress.place(x=-5,y=236)
        app_color='#249794'
        Frame(self,width=427,height=241,bg=app_color).place(x=0,y=0)  #249794
        b1=Button(self,width=10,height=1,text='Get Started',command=self.bar,border=0,fg=app_color,bg='white')
        b1.place(x=170,y=200)
 
        #labels
        l1=Label(self,text='SnowFall',fg='white',bg=app_color)
        lst1=('Calibri (Body)',18,'bold')
        l1.config(font=lst1)
        l1.place(x=70,y=80)
 
        l2=Label(self,text='production',fg='white',bg=app_color)
        lst2=('Calibri (Body)',18)
        l2.config(font=lst2)
        l2.place(x=182,y=82)
 
 
    def bar(self):
        try:
            r = session.get('https://ad.betcity.ru/d/off/champs?rev=4&ids_sp=1&ver=271&csn=ooca9s', headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0'})
        except requests.exceptions.ConnectionError as e:
            logging.warning(f"[bar] {e}")
            sleep(2)
            return
        soup = bs4(r.content, 'lxml').text
        json_ = json.loads(soup)
        pre_leagues = json_["reply"]["sports"]["1"]["chmps"]
        static_block = ['Киберфутбол','6х6','4х4','Статистика','минут.','БЕТСИТИ','Победитель','Кто выше по итогам чемпионата','Кол-во голов игрока','Какой игрок забьет больше голов','Количество игр','Итоги сезона','Продолжительность текущей серии команды','больше голов']
        r=0
        self.progress['maximum'] = len(pre_leagues)
        for league in pre_leagues:
            l4=Label(self,text=f'{r+1}/{len(pre_leagues)}',fg='white',bg=app_color)
            lst4=('Calibri (Body)',10)
            l4.config(font=lst4)
            l4.place(x=18,y=210)
            self.progress['value']=r
            self.update_idletasks()
 
            num = str(league) #ID события
            league_title = pre_leagues[num]["name_ch"].replace('Футбол.','').strip()#Название лиги
            blocked = False
            for block in static_block:
                if block in league_title:
                    blocked = True
            if blocked == False:
                date = pre_leagues[num]['next_ev']#Дата ближайшего матча
                match_date = datetime.strptime(date,'%Y-%m-%d %H:%M:%S')#преобразуем в необходимый формат
                check = db.check_leagues(league,league_title,match_date)
                if check is None:
                    future = datetime.today() + timedelta(days=3)#Берём нынышнее время в нужном формате
                    if future >= match_date:
                        db.add_leagues(league,league_title,match_date)
                else: pass
 
            r=r+1
        
        window = main_win()
        self.destroy()
Код Разводящей страницы (немного урезал т.к. большая часть парсинг при нажатии на соотв. кнопку):
Python
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
class main_win(Tk):
    def __init__(self):
        super().__init__()
        self.title('Разводящая страница')
        self.iconbitmap('files/SFlogo.ico')
        width_of_window = 427
        height_of_window = 250
        screen_width = self.winfo_screenwidth()
        screen_height = self.winfo_screenheight()
        x_coordinate = (screen_width/2)-(width_of_window/2)
        y_coordinate = (screen_height/2)-(height_of_window/2)
        self.geometry("%dx%d+%d+%d" %(width_of_window,height_of_window,x_coordinate,y_coordinate))
        #self.overrideredirect(1)
 
        s = ttk.Style()
        #s.theme_use('clam')
        #s.configure("red.Horizontal.TProgressbar", foreground='red', background='#4f4f4f')
        self.progress=Progressbar(self,style="red.Horizontal.TProgressbar",orient=HORIZONTAL,length=450,mode='determinate')
 
        self.progress.place(x=-5,y=236)
        Frame(self,width=427,height=241,bg=app_color).place(x=0,y=0)  #249794
        self.button_frame = LabelFrame(self, bg=app_color)
        self.button_frame.pack(fill='x', expand='yes', padx=20)
 
        #labels
        self.l=Label(self,text='',fg='white',bg=app_color)
        lst=('Calibri (Body)',10)
        self.l.config(font=lst)
        self.l.place(x=18,y=210)
 
        l1=Label(self,text='SnowFall',fg='white',bg=app_color)
        lst1=('Calibri (Body)',18,'bold')
        l1.config(font=lst1)
        l1.place(x=70,y=20)
 
        l2=Label(self,text='production',fg='white',bg=app_color)
        lst2=('Calibri (Body)',18)
        l2.config(font=lst2)
        l2.place(x=182,y=22)
 
        self.combo = Combobox(self.button_frame, values=['1','2','4','6','12','24','48'], width=5)
        self.combo.grid(row=0, column=0, padx=10, pady=10)
        self.combo.current(0)
        
        self.parse_button = Button(self.button_frame, text='Спарсить', command=self.parse)
        self.parse_button.grid(row=0, column=1, padx=10, pady=10)
 
        self.match_button = Button(self.button_frame, text='Матчи', command=self.fmatch)
        self.match_button.grid(row=1, column=0, padx=10, pady=10)
        
        self.config_button = Button(self.button_frame, text='Конфиг', command=self.config)
        self.config_button.grid(row=1, column=1, padx=10, pady=10)
 
        self.output_button = Button(self.button_frame, text='Очередь вывод', command=self.output)
        self.output_button.grid(row=1, column=2, padx=10, pady=10)
 
        self.history_button = Button(self.button_frame, text='История', command=self.history)
        self.history_button.grid(row=1, column=3, padx=10, pady=10)
 
    def parse(self):
        now = datetime.now()
        nbr = int(self.combo.get())
        logging.info(f"[Parse] Запустили парсинг за {nbr} часов")
        date = datetime.now().strftime('%d%m.%H%M')
        future = datetime.now()+timedelta(hours=nbr)
        db.clear_matches()
        val = db.get_leagues(now,future)
        self.progress['maximum'] = len(val)
        i=0
        try:
            session.get(f'https://betcity.ru/ru/line/soccer/', headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0'})
            for game in val:
                self.l.config(text='')
                self.l.config(text=f'{i+1}/{len(val)}')
                self.progress['value']=i
                self.update_idletasks()
                r = session.post('https://ad.betcity.ru/d/off/events?rev=6&ver=271&csn=ooca9s',params={'ids':game[0]}, headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0'})
                sleep(0.1)
                soup = bs4(r.content, 'lxml').text
                json_ = json.loads(soup)
                try:
                    matches = json_["reply"]["sports"]["1"]["chmps"][str(game[0])]["evts"]
                except KeyError as e:
                    logging.warning(f'[Parse] - {e}')
                    matches = None
                if matches is not None:
                    for match in matches:
                        *куча условия и парсинг*
                i+=1
        except requests.exceptions.ConnectionError:
            logging.warning(f'[ConnectionError]')
            sleep(1)
        self.l.config(text='Парсинг завершён')
        self.l.config(text='Начинаем запись файла')
        info = db.get_output()
        *создание и запись xls*
        self.l.config(text='Запись файла завершена')
        self.progress['value']=0
        self.update_idletasks()
        sleep(1)
        self.l.config(text='')
 
 
    def fmatch(self):
        window = fmatch_win()
        window.grab_set()
        #self.destroy()
 
    def config(self):
        window = config_win()
        window.grab_set()
        #self.destroy()
 
    def output(self):
        window = output_win()
        window.grab_set()
        #self.destroy()
 
    def history(self):
        window = history_win()
        window.grab_set()
Код одного из дочерних окон:
Python
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
class config_win(Tk):
    def __init__(self):
        super().__init__()
        val = db.get_config()
        self.title('Настройки')
        self.iconbitmap('files/SFlogo.ico')
        self.button_text='Сохранить'
        self.entry_font=('Calibri (Body)',14)
        self.label_font=('Calibri (Body)',14)
        app_color='#249794'
 
        width_of_window = 700
        height_of_window = 275
        screen_width = self.winfo_screenwidth()
        screen_height = self.winfo_screenheight()
        x_coordinate = (screen_width/2)-(width_of_window/2)
        y_coordinate = (screen_height/2)-(height_of_window/2)
        self.geometry("%dx%d+%d+%d" %(width_of_window,height_of_window,x_coordinate,y_coordinate))
        #self.overrideredirect(1)
 
        Frame(self,width=700,height=275,bg=app_color).place(x=0,y=0)  #249794
 
        '''Поля ввода текста / lineEdit'''
        self.token = ttk.Entry(self)
        self.token.config(font=self.entry_font, justify="center", foreground=app_color)
        self.token.place(x=80,y=10,width=510)
        self.token.insert(0,val[0])
 
        self.chat_id_1t = ttk.Entry(self)
        self.chat_id_1t.config(font=self.entry_font, justify="center", foreground=app_color)
        self.chat_id_1t.place(x=160,y=40,width=160)
        self.chat_id_1t.insert(0,val[1])
 
        self.chat_id_2t = ttk.Entry(self)
        self.chat_id_2t.config(font=self.entry_font, justify="center", foreground=app_color)
        self.chat_id_2t.place(x=160,y=70,width=160)
        self.chat_id_2t.insert(0,val[2])
 
        self.check_time_1t = ttk.Entry(self)
        self.check_time_1t.config(font=self.entry_font, justify="center", foreground=app_color)
        self.check_time_1t.place(x=170,y=100,width=30)
        self.check_time_1t.insert(0,val[3])
        
        self.check_time_2t = ttk.Entry(self)
        self.check_time_2t.config(font=self.entry_font, justify="center", foreground=app_color)
        self.check_time_2t.place(x=170,y=130,width=30)
        self.check_time_2t.insert(0,val[4])
 
        self.good_score_2t = ttk.Entry(self)
        self.good_score_2t.config(font=self.entry_font, justify="center", foreground=app_color)
        self.good_score_2t.place(x=80,y=160,width=510)
        self.good_score_2t.insert(0,val[6])
 
        '''Кнопки записи в базу'''
        self.token_saveButton = ttk.Button(text=self.button_text,command=lambda:self.bd_add(['token',self.token.get()]))
        self.token_saveButton.place(x=600,y=10,height=29)
 
        self.chat_id_1t_saveButton = ttk.Button(text=self.button_text,command=lambda:self.bd_add(['chat_id_1t',self.chat_id_1t.get()]))
        self.chat_id_1t_saveButton.place(x=330,y=40,height=29)
 
        self.chat_id_2t_saveButton = ttk.Button(text=self.button_text,command=lambda:self.bd_add(['chat_id_2t',self.chat_id_2t.get()]))
        self.chat_id_2t_saveButton.place(x=330,y=70,height=29)
 
        self.check_time_1t_saveButton = ttk.Button(text=self.button_text,command=lambda:self.bd_add(['check_time_1t',self.check_time_1t.get()]))
        self.check_time_1t_saveButton.place(x=210,y=100,height=29)
 
        self.check_time_2t_saveButton = ttk.Button(text=self.button_text,command=lambda:self.bd_add(['check_time_2t',self.check_time_2t.get()]))
        self.check_time_2t_saveButton.place(x=210,y=130,height=29)
 
        self.good_score_2t_saveButton = ttk.Button(text=self.button_text,command=lambda:self.bd_add(['good_score_2t',self.good_score_2t.get()]))
        self.good_score_2t_saveButton.place(x=600,y=160,height=29)
 
        self.button_frame = LabelFrame(self, text='Управление',fg='white',bg=app_color)
        self.button_frame.pack(fill='x', expand='yes', padx=20, pady=10 ,anchor=S)
 
        self.main_button = Button(self.button_frame, text='Главная',bg='white', command=self.main)
        self.main_button.grid(row=0, column=0, padx=10, pady=10)
 
        '''Надписи слева от полей ввода / Label'''
        self.l1=Label(self,text='Token',fg='white',bg=app_color)
        self.l1.config(font=self.label_font)
        self.l1.place(x=10,y=10)
 
        self.l2=Label(self,text='ID чата 1й тайм',fg='white',bg=app_color)
        self.l2.config(font=self.label_font)
        self.l2.place(x=10,y=40)
 
        self.l3=Label(self,text='ID чата 2й тайм',fg='white',bg=app_color)
        self.l3.config(font=self.label_font)
        self.l3.place(x=10,y=70)
 
        self.l4=Label(self,text='Время 1го тайма',fg='white',bg=app_color)
        self.l4.config(font=self.label_font)
        self.l4.place(x=10,y=100)
 
        self.l5=Label(self,text='Время 2го тайма',fg='white',bg=app_color)
        self.l5.config(font=self.label_font)
        self.l5.place(x=10,y=130)
 
        self.l6=Label(self,text='Счета',fg='white',bg=app_color)
        self.l6.config(font=self.label_font)
        self.l6.place(x=10,y=160)
 
    def main(self):
        #window = main_win()
        self.destroy()
 
    def bd_add(self,value):
      answer = mb.askyesno(
      title="Внимание!", 
      message=f"Вы уверены в изменении данных?")
      if answer:
        q = db.upd_config(value)
        if q['status'] == 'Ok':
          mb.showinfo(title='Успех', message='Операция успешно совершена')
        else:
          mb.showerror(title='Ошибка', message=q['body'])
0
290 / 205 / 68
Регистрация: 18.09.2019
Сообщений: 407
Записей в блоге: 58
13.05.2022, 01:01
Xerber, вот это номер... Как Вы лихо воспроизводите все окна от корневого tkinter.Tk(). Ну, не стоит этого делать-то!

Такое окно (главное окно), по определению, может быть только в одном единственном экземпляре на всё приложение, как и соответствующий ему главный цикл обработки событий mainloop. А требующиеся дополнительные окна верхнего верхнего уровня вообще-то создаются с помощью tkinter.TopLevel().

Я даже не буду дальше смотреть (уж, простите), только добавлю, что в tkinter за графический интерфейс отвечает именно главный поток. Дополнительные потоки могут только готовить какие-то данные, на основании которых главный поток будет создавать и отрисовывать элементы графического интерфейса. А тот пример, что я Вам привёл, как раз наглядно показывает до чего доводит нарушение этого правила.

Вы свой код знаете лучше. Посмотрите, не запускаете ли Вы какое-нибудь окно в дополнительном потоке, как это сделал автор вышеуказанного примера, не изменяете ли Вы элементы GUI непосредственно из дополнительного потока и т.д. Ведь все операции по созданию, изменению и удалению элементов GUI должны выполняться именно из главного потока.

Наверняка можно найти массу примеров здесь или на других ресурсах...
0
0 / 0 / 0
Регистрация: 09.05.2022
Сообщений: 7
13.05.2022, 14:08  [ТС]
Спасибо за информацию про tkinter.TopLevel(), я обязательно изучу данный вопрос и перепишу код.

> в tkinter за графический интерфейс отвечает именно главный поток. Дополнительные потоки могут только готовить какие-то данные, на основании которых главный поток будет создавать и отрисовывать элементы графического интерфейса.
Это я читал и мой второй поток никаким образом не взаимодействует с gui, только вносит изменения в базу.

mainloop так же присутствует только в 1 экземпляре, более нигде не упоминается. Дополнительные потоки так же не создаются не в каком окне
0
290 / 205 / 68
Регистрация: 18.09.2019
Сообщений: 407
Записей в блоге: 58
13.05.2022, 16:14
Xerber, не в обиду, но косяк-то с tkinter.Tk() настолько явный, что грех было на него не указать. С другой стороны, расчитывать тут на полный аудит такого объёмного кода тоже, наверное, не следует, но помочь, по крайней мере, попытаюсь. Вот Вы всё прошерстили и железно уверены, что GUI тут не при чём. С другой стороны, нет никаких данных о том, что такая ситуация может возникать только исключительно из-за GUI. Может быть виноват кто-то другой?

Хотя я уже, конечно, как говорится "не знал да и забыл" этот tkinter, что-то он мне сразу не пошёл, поэтому то, что отмечу сейчас может тоже оказаться не при чём.

В коде окон и в коде дополнительного потока встречаются некие конструкции примерно такого вида:
Python
1
    r = db.get_config()
которые ссылаются на некую db. Откуда она возникает, как её добывают - непонятно, хотя судя по контексту, это некое соединение с базой данных. И, насколько я понял, одно и тоже соединение используется для работы с БД как в главном потоке, так и в дополнительном. Создано и открыто оно было, скорее всего, в контексте главного потока, а используется одновременно и в главном, и в дополнительном. Возникает вопрос - а это легитимно? Например, в Qt/PyQt такие кунштюки сразу не рекомендовали, а с версии 5.6 (?) просто запретили на уровне кода, вываливая ошибку. А как здесь? Может это послужить причиной появления подобной ошибки? Да, легко...

Я бы на Вашем месте не парился с полноценным приложением, а слепил бы для начала легковесную модельку, чтобы отработать все тёмные места. Ну, Вы в блог ко мне заглядывали - видели чего я там творю
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
13.05.2022, 16:14
Помогаю со студенческими работами здесь

Threading.Thread Передача значений методу
Доброго времени суток! Снизу код! Кто знает как правильно передать методу нужный тип??? Private Function LoadListVideoGroup() As...

Can't create handler inside thread that has not called Looper.prepare()
Привет, с Новым Годом! Подскажите почему приложение вылетает с ошибкой при показе progressdialog. Ошибка: ...

Can't create handler inside thread that has not called Looper.prepare()
Привет всем. У меня есть програмка которая должна принимать Json, тут я использую AsyncTask и библиотеку Ok3Http. Проблема заключается в...

Thread vs async
Thread vs async в чем разница?)

System.Threading.Thread.Sleep запускается не там, где прописан
Здравствуйте! У меня есть форма, на ней различные "label", из которых состоит уровень, а есть "label" во всю программу, на...


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

Или воспользуйтесь поиском по форуму:
13
Ответ Создать тему
Новые блоги и статьи
модель ЗдравоСохранения 8. Подготовка к разному выполнению заданий
anaschu 08.04.2026
https:/ / github. com/ shumilovas/ med2. git main ветка * содержимое блока дэлэй из старой модели теперь внутри зайца новой модели 8ATzM_2aurI
Блокировка документа от изменений, если он открыт у другого пользователя
Maks 08.04.2026
Алгоритм из решения ниже реализован на примере нетипового документа, разработанного в конфигурации КА2. Задача: запретить редактирование документа, если он открыт у другого пользователя. / / . . .
Система безопасности+живучести для сервера-слоя интернета (сети). Двойная привязка.
Hrethgir 08.04.2026
Далее были размышления о системе безопасности. Сообщения с наклонным текстом - мои. А как нам будет можно проверить, что ссылка наша, а не подделана хулиганами, которая выбросит на другую ветку и. . .
Модель ЗдрввоСохранения 7: больше работников, больше ресурсов.
anaschu 08.04.2026
работников и заданий может быть сколько угодно, но настроено всё так, что используется пока что только 20% kYBz3eJf3jQ
Дальние перспективы сервера - слоя сети с космологическим дизайном интефейса карты и логики.
Hrethgir 07.04.2026
Дальнейшее ближайшее планирование вывело к размышлениям над дальними перспективами. И вот тут может быть даже будут нужны оценки специалистов, так как в дальних перспективах всё может очень сильно. . .
Горе от ума
kumehtar 07.04.2026
Эта мне ментальная установка, что вот прямо сейчас, мол, мне для полного счастья не хватает (нужное вписать), и когда я этого достигну - тогда и полный кайф. Одна из самых сильных ловушек на пути. . . .
Использование значений реквизитов справочника в документе, с определенными условиями и правами
Maks 07.04.2026
1. Контроль срока действия договора Алгоритм из решения ниже реализован на примере нетипового документа "ЗаявкаНаРаботу", разработанного в конфигурации КА2. Задача: уведомлять пользователя, если. . .
Доступность команды формы по условию
Maks 07.04.2026
Алгоритм из решения ниже реализован на примере нетипового документа "СписаниеМатериалов", разработанного в конфигурации КА2. Задача: сделать доступной кнопку (команда формы "ЗавершитьСписание") при. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru