PyQt5+yt-dlp多线程下载出现exit code -1073741819错误
PyQt5 + yt-dlp 线程下载时出现0xC0000005访问冲突错误
基于PyQt5开发YouTube视频下载工具,使用yt-dlp实现下载功能。通过线程执行download_thread函数时,会随机触发Process finished with exit code -1073741819 (0xC0000005)错误;部分场景下下载任务可完整执行,但结束后仍会触发该错误。若不使用线程直接运行函数,下载可正常完成,但程序界面会失去响应。
函数实现
def DOWNLOAD(self): if not self.LINEEDIT_LINK.text() == '' and _FILE_SIZE > 0: self.PUSHBUTTON_DOWNLOAD.setEnabled(False) self.PUSHBUTTON_DOWNLOAD.setStyleSheet(set_download_button_font_size(14)) downloadThread = Thread(target=self.download_thread) downloadThread.daemon = True downloadThread.start() def my_hook(self, d): if d['status'] == 'downloading': self.__cleen_percent(dl=d) elif d['status'] == 'finished': self.PROGRESSBAR.setValue(0) self.PUSHBUTTON_DOWNLOAD.setText("Download") self.PUSHBUTTON_DOWNLOAD.setCheckable(True) def __cleen_percent(self, dl): network_speed = dl['_speed_str'] percent = dl['_percent_str'] all_mb = round(dl['total_bytes_estimate'] / 1024 / 1024, 2) downloaded_mb = round(dl['downloaded_bytes'] / 1024 / 1024, 2) eta = dl['eta'] for i, char in enumerate(percent): if char == '%': value = percent[i - 5:i + 1].replace('%', '') value = (float(value.replace(' ', ''))) self.PROGRESSBAR.setValue(int(value)) self.PUSHBUTTON_DOWNLOAD.setText( f'{network_speed} | {downloaded_mb} MB / {all_mb} MB | eta: {eta} secs') break # YOUTUBE THREADS def download_thread(self): try: ydl_opts_download = { 'outtmpl': FOLDER_TO_SAVE + '/%(title)s.%(ext)s', 'format': f"bv*[height<={_RESOLUTION}][ext={_VIDEO_FORMAT}]+ba[ext=m4a]/b[height<={_RESOLUTION}][ext={_VIDEO_FORMAT}] / bv*+ba/b", 'progress_hooks': [self.my_hook], } ydl_opts_download_auto = { 'outtmpl': FOLDER_TO_SAVE + '/%(title)s.%(ext)s', 'format': f"bv*[height<={_RESOLUTION}]+ba[ext=m4a]/b[height<={_RESOLUTION}] / bv*+ba/b", 'progress_hooks': [self.my_hook], } ydl_opts_download_maxres = { 'outtmpl': FOLDER_TO_SAVE + '/%(title)s.%(ext)s', 'format': f"bv*[ext={_VIDEO_FORMAT}]+ba[ext=m4a]/b[ext={_VIDEO_FORMAT}] / bv*+ba/b", 'progress_hooks': [self.my_hook], } ydl_opts_download_maxres_auto = { 'outtmpl': FOLDER_TO_SAVE + '/%(title)s.%(ext)s', 'format': f"bv*+ba[ext=m4a]/b / bv*+ba/b", 'progress_hooks': [self.my_hook], } if FILE_TYPE == 0: if MAX_RESOLUTION is True: with YoutubeDL(ydl_opts_download_maxres_auto) as ydl: ydl.download([_URL]) else: with YoutubeDL(ydl_opts_download_auto) as ydl: ydl.download([_URL]) else: if MAX_RESOLUTION is True: with YoutubeDL(ydl_opts_download_maxres) as ydl: ydl.download([_URL]) else: with YoutubeDL(ydl_opts_download) as ydl: ydl.download([_URL]) if OPEN_FOLDER is True: startfile(FOLDER_TO_SAVE) if NOTIFICATIONS is True: match LANGUAGE: case 0: toast.show_toast( "Youtube Downloader", "Video downloaded", duration=5, threaded=True, icon_path=load_file("icons/youtube-downloader-icon.ico"), ) case 1: toast.show_toast( "Youtube Downloader", "Видео скачано", duration=5, threaded=True, icon_path=load_file("icons/youtube-downloader-icon.ico"), ) if CLOSE_APP is True: QCoreApplication.quit() self.PUSHBUTTON_DOWNLOAD.setEnabled(True) self.PUSHBUTTON_DOWNLOAD.setStyleSheet(set_download_button_font_size(16)) except Exception as e: print(e) self.PUSHBUTTON_DOWNLOAD.setEnabled(True) self.PUSHBUTTON_DOWNLOAD.setStyleSheet(set_download_button_font_size(16))
终端输出
[youtube] YBBbNbumaoM: Downloading webpage [youtube] YBBbNbumaoM: Downloading android player API JSON [youtube] YBBbNbumaoM: Downloading MPD manifest [info] YBBbNbumaoM: Downloading 1 format(s): 248+140 [dashsegments] Total fragments: 111 [download] Destination: E:\code\PycharmProjects\pythonProject\YOUTUBE_DOWNLOADER_2\videos\Free Bird Isolated Guitar Track HQ.f248.webm [download] 37.8% of ~ 11.91MiB at 933.49KiB/s ETA 00:08 (frag 42/111)Windows fatal exception: access violation Thread 0x00002544 (most recent call first): File "E:\code\PycharmProjects\pythonProject\YOUTUBE_DOWNLOADER_2\downloader.py", line 1184 in __cleen_percent File "E:\code\PycharmProjects\pythonProject\YOUTUBE_DOWNLOADER_2\downloader.py", line 1166 in my_hook File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\common.py", line 459 in _hook_progress File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\fragment.py", line 284 in frag_progress_hook File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\common.py", line 459 in _hook_progress File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\http.py", line 314 in download File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\http.py", line 377 in real_download File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\common.py", line 444 in download File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\fragment.py", line 124 in _download_fragment File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\fragment.py", line 469 in download_fragment File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\fragment.py", line 521 in download_and_append_fragments File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\fragment.py", line 382 in download_and_append_fragments_multiple File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\dash.py", line 60 in real_download File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\common.py", line 444 in download File "E:\code\Python\Lib\site-packages\yt_dlp\YoutubeDL.py", line 2970 in dl File "E:\code\Python\Lib\site-packages\yt_dlp\YoutubeDL.py", line 3229 in process_info File "E:\code\Python\Lib\site-packages\yt_dlp\YoutubeDL.py", line 2779 in process_video_result File "E:\code\Python\Lib\site-packages\yt_dlp\YoutubeDL.py", line 1674 in process_ie_result File "E:\code\Python\Lib\site-packages\yt_dlp\YoutubeDL.py", line 1615 in __extract_info File "E:\code\Python\Lib\site-packages\yt_dlp\YoutubeDL.py", line 1518 in wrapper File "E:\code\Python\Lib\site-packages\yt_dlp\YoutubeDL.py", line 1507 in extract_info File "E:\code\Python\Lib\site-packages\yt_dlp\YoutubeDL.py", line 3344 in wrapper File "E:\code\Python\Lib\site-packages\yt_dlp\YoutubeDL.py", line 3369 in download File "E:\code\PycharmProjects\pythonProject\YOUTUBE_DOWNLOADER_2\downloader.py", line 1222 in download_thread File "E:\code\Python\Lib\threading.py", line 975 in run File "E:\code\Python\Lib\threading.py", line 1038 in _bootstrap_inner File "E:\code\Python\Lib\threading.py", line 995 in _bootstrap Current thread 0x00002cb0 (most recent call first): File "E:\code\PycharmProjects\pythonProject\YOUTUBE_DOWNLOADER_2\downloader.py", line 1451 in <module> [download] 39.6% of ~ 11.92MiB at 49.16KiB/s ETA 00:11 (frag 44/111) Process finished with exit code -1073741819 (0xC0000005)
问题根源与修复方案
问题根源
错误0xC0000005是Windows下的内存访问冲突,核心原因是PyQt的UI组件只能在主线程(GUI线程)中操作。你的my_hook和__cleen_percent函数直接在子线程中修改PROGRESSBAR、PUSHBUTTON_DOWNLOAD等UI元素,违反了线程安全规则,导致内存访问异常。
修复方案
方案1:改用QThread + 信号槽(官方推荐)
通过自定义QThread子类,用信号传递进度信息,主线程接收信号后更新UI,确保所有UI操作在主线程执行。
from PyQt5.QtCore import QThread, pyqtSignal # 自定义下载线程类 class DownloadThread(QThread): progress_updated = pyqtSignal(dict) # 传递进度信息的信号 download_finished = pyqtSignal() # 下载完成信号 download_failed = pyqtSignal(str) # 下载失败信号 def __init__(self, url, folder, file_type, max_res, resolution, video_format): super().__init__() self.url = url self.folder = folder self.file_type = file_type self.max_res = max_res self.resolution = resolution self.video_format = video_format def run(self): try: # 进度钩子:发射信号而非直接操作UI def progress_hook(d): self.progress_updated.emit(d) # 构建yt-dlp配置 ydl_opts_base = { 'outtmpl': f"{self.folder}/%(title)s.%(ext)s", 'progress_hooks': [progress_hook], } if self.file_type == 0: if self.max_res: ydl_opts_base['format'] = "bv*+ba[ext=m4a]/b / bv*+ba/b" else: ydl_opts_base['format'] = f"bv*[height<={self.resolution}]+ba[ext=m4a]/b[height<={self.resolution}] / bv*+ba/b" else: if self.max_res: ydl_opts_base['format'] = f"bv*[ext={self.video_format}]+ba[ext=m4a]/b[ext={self.video_format}] / bv*+ba/b" else: ydl_opts_base['format'] = f"bv*[height<={self.resolution}][ext={self.video_format}]+ba[ext=m4a]/b[height<={self.resolution}][ext={self.video_format}] / bv*+ba/b" with YoutubeDL(ydl_opts_base) as ydl: ydl.download([self.url]) self.download_finished.emit() except Exception as e: self.download_failed.emit(str(e)) # 原DOWNLOAD方法修改 def DOWNLOAD(self): if not self.LINEEDIT_LINK.text() == '' and _FILE_SIZE > 0: self.PUSHBUTTON_DOWNLOAD.setEnabled(False) self.PUSHBUTTON_DOWNLOAD.setStyleSheet(set_download_button_font_size(14)) # 创建线程实例并传入参数 self.download_thread = DownloadThread( url=_URL, folder=FOLDER_TO_SAVE, file_type=FILE_TYPE, max_res=MAX_RESOLUTION, resolution=_RESOLUTION, video_format=_VIDEO_FORMAT ) # 连接信号到主线程槽函数 self.download_thread.progress_updated.connect(self.handle_progress_update) self.download_thread.download_finished.connect(self.handle_download_finished) self.download_thread.download_failed.connect(self.handle_download_failed) self.download_thread.start() # 主线程处理进度更新的槽函数 def handle_progress_update(self, d): if d['status'] == 'downloading': self.__cleen_percent(dl=d) elif d['status'] == 'finished': self.PROGRESSBAR.setValue(0) self.PUSHBUTTON_DOWNLOAD.setText("Download") self.PUSHBUTTON_DOWNLOAD.setCheckable(True) # 主线程处理下载完成的槽函数 def handle_download_finished(self): if OPEN_FOLDER is True: startfile(FOLDER_TO_SAVE) if NOTIFICATIONS is True: match LANGUAGE: case 0: toast.show_toast( "Youtube Downloader", "Video downloaded", duration=5, threaded=True, icon_path=load_file("icons/youtube-downloader-icon.ico"), ) case 1: toast.show_toast( "Youtube Downloader", "Видео скачано", duration=5, threaded=True, icon_path=load_file("icons/youtube-downloader-icon.ico"), ) if CLOSE_APP is True: QCoreApplication.quit() self.PUSHBUTTON_DOWNLOAD.setEnabled(True) self.PUSHBUTTON_DOWNLOAD.setStyleSheet(set_download_button_font_size(16)) # 主线程处理下载失败的槽函数 def handle_download_failed(self, error_msg): print(error_msg) self.PUSHBUTTON_DOWNLOAD.setEnabled(True) self.PUSHBUTTON_DOWNLOAD.setStyleSheet(set_download_button_font_size(16))
方案2:用QMetaObject.invokeMethod在主线程更新UI
若不想重构线程逻辑,可在原钩子函数中用QMetaObject.invokeMethod包装UI操作,强制其在主线程执行:
from PyQt5.QtCore import QMetaObject, Qt def __cleen_percent(self, dl): network_speed = dl['_speed_str'] percent = dl['_percent_str'] all_mb = round(dl['total_bytes_estimate'] / 1024 / 1024, 2) downloaded_mb = round(dl['downloaded_bytes'] / 1024 / 1024, 2) eta = dl['eta'] for i, char in enumerate(percent): if char == '%': value = percent[i - 5:i + 1].replace('%', '') value = int(float(value.replace(' ', ''))) text = f'{network_speed} | {downloaded_mb} MB / {all_mb} MB | eta: {eta} secs' # 强制在主线程更新进度条 QMetaObject.invokeMethod(self.PROGRESSBAR, "setValue", Qt
相关产品推荐
相关产品推荐

