Client instances conflict in PyQT5 thread
MiHaKun opened this issue · 1 comments
Code that causes the issue
I don't know whether it is a "bug"( I have upgrade) . If I shouldn't have posted this question in this section, I apologize. Please let me know where it would be more appropriate. This issue seems to be related to a conflict.
To avoid blocking the normal operation of the UI, I start a thread when the user clicks a button. The thread uses asyncio event loop execute an asynchronous entry program.
The following code is a simplified version of the implementation of this process.
WinMain
def OnBtnStartClick(self):
self.btnStart.setEnabled(False)
for session in self.sessions:
self.session_queue.put(session) # type: queue.Queue()
threading.Thread(target=self.BGThreadBody, args=(features,)).start()
def BGThreadBody(self, features):
self.loop = asyncio.new_event_loop()
self.loop.run_until_complete(self._run_tasks(features))
async def _run_tasks(self, features):
cs = []
for idx in range(self.uiThreadCount.value()): # 1 : works well , 2: exception popup ..
task = TelegramFeatureTask(
name=str(idx + 1),
session_queue=self.session_queue,
)
c = self.loop.create_task(task._run_async())
self.tasks.append(task)
cs.append(c)
await asyncio.gather(*cs)
feature
class TelegramFeatureTask:
def __init__(
self,
name: str,
session_queue: Queue):
self.session_queue = session_queue
self.name = name
async def _run_async(self):
while not self.session_queue.empty():
s = self.session_queue.get(timeout=1)
if not s:
break
tg_session = StringSession(s)
client = TelegramClient(
session=tg_session,
**config['login_param'] # simple read from config file
)
await asyncio.wait_for(client.connect(), timeout=10)
# await client.connect()
me = await client.get_me()
if not me:
raise NoMeError()
upload = await self.client.upload_file(config['avatars'][self.name])
photo = await self.client(functions.photos.UploadProfilePhotoRequest(file=upload))
await client.disconnect()
these code works well in console multithread . But failed in PyQT thread .
upload = await client.upload_file(avatar)
photo = await client(functions.photos.UploadProfilePhotoRequest(file=upload))
Exception detail .
File part 0 missing (caused by UploadProfilePhotoRequest)
Sleeping for 4s (0:00:04) on UpdateProfilePhotoRequest flood wait
Expected behavior
no exceptions .
Actual behavior
File part 0 missing (caused by UploadProfilePhotoRequest)
Sleeping for 4s (0:00:04) on UpdateProfilePhotoRequest flood wait
Traceback
I debug this problem for days .
I add log in telethon's UserMethods
call and found there mightbe a conflict .
the log code added in telethon/client/users.py
:
class UserMethods:
async def __call__(self: 'TelegramClient', request, ordered=False, flood_sleep_threshold=None):
return await self._call(self._sender, request, ordered=ordered)
async def _call(self: 'TelegramClient', sender, request, ordered=False, flood_sleep_threshold=None):
# print(f"self: {id(self)} equl:{id(asyncio.get_running_loop()) == id(self._loop)} running_loop: {id(asyncio.get_running_loop())} , self._loop: {id(self._loop)} request: {type(request)}`{id(request)}` sender: {type(sender)}`{id(sender)}`")
print(f"self: {id(self)} request: {type(request)}`{id(request)}` sender: {type(sender)}`{id(sender)}`")
if self._loop is not None and self._loop != helpers.get_running_loop():
raise RuntimeError('The asyncio event loop must not change after connection (see the FAQ for details)')
# if the loop is None it will fail with a connection error later on
if flood_sleep_threshold is None:
flood_sleep_threshold = self.flood_sleep_threshold
requests = (request if utils.is_list_like(request) else (request,))
for r in requests:
if not isinstance(r, TLRequest):
raise _NOT_A_REQUEST()
await r.resolve(self, utils)
# ...............
# ...............
I print the self(client) 's id, self._loop id (never changed) , runningloop is equl self._loop ( they are always the same ) request type and id , sender's id .
- the task count is 2 .
- the get_me/ upload 's request log is right .
- the uploadprofilerequest's log is confilct . the second task's instance changed to the first task's instance and caused error TypeInputPhoto and flood rpc error .
I read the FAQ and Compatibility and Convenience document .
I understand that the esteemed author has advised everyone to avoid using threads whenever possible. However, it seems that in GUI programs, the only way to prevent blocking is to start a thread. Therefore, I would still appreciate any suggestions regarding this issue. Thank you.
Btw: In fact , my oldest code was run each client in thread's eventloop (multithread which have one loop each) . it caused " RunningTimeError(The asyncio event loop must not change after connection (see the FAQ for details)) " , but the code works well in console application too ....
Telethon version
1.38.1
Python version
3.12.7
Operating system (including distribution name and version)
Windows 11
Other details
PyQt5==5.15.11
PyQt5-Qt5==5.15.2
PyQt5_sip==12.15.0
Checklist
- The error is in the library's code, and not in my own.
- I have searched for this issue before posting it and there isn't an open duplicate.
- I ran
pip install -U https://github.com/LonamiWebs/Telethon/archive/v1.zip
and triggered the bug in the latest version.