鱼C论坛

 找回密码
 立即注册
查看: 1077|回复: 6

[已解决]关于多线程下载 暂停 跟恢复问题

[复制链接]
发表于 2023-5-11 16:06:38 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
写个一个多线程下载,
但是无法暂停跟恢复,

老哥们帮忙处理下这个问题。


  1. import json
  2. import os
  3. import requests
  4. import threading
  5. import tkinter as tk
  6. from tkinter import filedialog,messagebox
  7. from concurrent.futures import ThreadPoolExecutor
  8. import logging

  9. # 创建日志目录
  10. # os.makedirs('logs', exist_ok=True)
  11. # logging.basicConfig(filename='logs/download.log', level=logging.ERROR)

  12. # BASE_DIR = 'BASE_DIR'  # 下载文件的基本目录,你需要根据需要进行修改

  13. # 定义下载函数
  14. def download_file(download_url, directory, lock, event, label, count):
  15.    

  16.     try:
  17.         # 获取下载文件的文件名
  18.         filename = os.path.basename(download_url)
  19.         print('正在下载文件:', filename )
  20.         base_url = url_entry.get()
  21.         # 创建目录
  22.         os.makedirs(directory, exist_ok=True)
  23.         # 下载文件
  24.         response = requests.get(base_url+"/webres/"+download_url, timeout=10)
  25.         with lock:
  26.             with open(os.path.join(directory, filename), 'wb') as f:
  27.                 f.write(response.content)
  28.         print(f'文件 {download_url} 下载结束')
  29.       
  30.         label.set(f'正在下载文件:{filename}+下载文件数量:{count}')  # 更新窗口中的标签
  31.         


  32.     except Exception as e:
  33.         print(f'下载文件 {base_url+download_url} 出错: {e}')
  34.         logging.error(f'下载文件 {download_url} 出错')
  35.     finally:
  36.         event.set()  # 下载结束后设置 event

  37. # 定义选择文件函数
  38. def choose_file():
  39.     file_path = filedialog.askopenfilename()
  40.     file_path_var.set(file_path)
  41.     # 读取本地json文件
  42.     with open(file_path, 'r') as f:
  43.         data = json.load(f)
  44.     # 打印下载地址数量
  45.     count_1 = len(data)
  46.    

  47.     print('下载地址数量:', len(data),count_1)

  48. # 定义下载函数
  49. def download(max_threads):
  50.     # 检查用户是否已经选择了json文件
  51.     if not file_path_var.get():
  52.         messagebox.showinfo('提示', '请先选择json文件')
  53.         return

  54.     # 读取本地json文件


  55. #创建日志目录
  56.     os.makedirs(url_entry.get()+'/logs', exist_ok=True)
  57.     logging.basicConfig(filename=url_entry.get()+'/logs/download.log', level=logging.ERROR)

  58.     with open(file_path_var.get(), 'r') as f:
  59.         data = json.load(f)
  60.     # 创建锁对象
  61.     lock = threading.Lock()
  62.     # 创建线程池
  63.     with ThreadPoolExecutor(max_workers=max_threads) as executor:
  64.         # 创建 event 对象
  65.         event = threading.Event()
  66.         event.set()  # 初始状态为 True
  67.         # 创建 tkinter.StringVar() 对象
  68.         label_var = tk.StringVar()
  69.         label_var.set('等待下载...')
  70.         label = tk.Label(window, textvariable=label_var)
  71.         label.pack()

  72.         # 遍历下载地址并下载文件
  73.         count = 0
  74.         for download_url in data:
  75.             # 获取下载文件的目录
  76.             count += 1
  77.             directory = os.path.join(url_entry.get(), *download_url.split('/')[1:-1])
  78.             # 提交任务到线程池
  79.             executor.submit(download_file, download_url, directory, lock, event, label_var,count)
  80.             # 检查 event 对象的状态,如果为 False,则暂停下载
  81.             while not event.is_set():
  82.                 pass

  83. # 定义暂停下载函数
  84. def pause_download():
  85.     event.clear()

  86. # 定义恢复下载函数
  87. def resume_download():
  88.     event.set()

  89. # 创建窗口
  90. window = tk.Tk()
  91. window.title('批量下载')
  92. window.geometry('400x300')

  93. # 创建选择文件按钮
  94. file_path_var = tk.StringVar()
  95. file_path_label = tk.Label(window, textvariable=file_path_var)
  96. file_path_label.pack()
  97. choose_file_button = tk.Button(window, text='选择文件', command=choose_file)
  98. choose_file_button.pack()

  99. # 创建线程数量标签和文本框
  100. thread_label = tk.Label(window, text='线程数量:')
  101. thread_label.pack()
  102. thread_entry = tk.Entry(window)
  103. thread_entry.insert(0, '10')
  104. thread_entry.pack()

  105. # 创建下载地址标签和文本框
  106. url_label = tk.Label(window, text='下载地址:')
  107. url_label.pack()

  108. url_entry = tk.Entry(window)
  109. url_entry.pack()
  110. url_entry.insert(0, '6290')



  111. # 创建下载按钮
  112. download_button = tk.Button(window, text='下载', command=lambda: threading.Thread(target=download, args=(int(thread_entry.get()),)).start())
  113. download_button.pack()

  114. # 创建暂停按钮和恢复按钮
  115. pause_button = tk.Button(window, text='暂停', command=pause_download)
  116. pause_button.pack()
  117. resume_button = tk.Button(window, text='恢复', command=resume_download)
  118. resume_button.pack()
  119. download_button.place(x=130, y=160)
  120. pause_button.place(x=180, y=160)
  121. resume_button.place(x=230, y=160)

  122. # 运行窗口
  123. window.mainloop()
复制代码
最佳答案
2023-5-11 18:42:38
koma610630 发表于 2023-5-11 18:32
"event" is not definedPylance
..报错
  1. import logging
  2. import threading
  3. from concurrent.futures import ThreadPoolExecutor
  4. import json
  5. import os
  6. import tkinter as tk
  7. from tkinter import filedialog, messagebox

  8. def download(max_threads):
  9.     # 检查用户是否已经选择了json文件
  10.     if not file_path_var.get():
  11.         messagebox.showinfo('提示', '请先选择json文件')
  12.         return

  13.     # 读取本地json文件


  14.     os.makedirs(url_entry.get()+'/logs', exist_ok=True)
  15.     logging.basicConfig(filename=url_entry.get()+'/logs/download.log', level=logging.ERROR)

  16.     with open(file_path_var. get(), 'r') as f:
  17.         data = json.load(f)

  18.     lock = threading.Lock()
  19.    
  20.     # 创建事件对象
  21.     event = threading.Event()
  22.     event.set()

  23.     # 定义下载标志
  24.     downloading = True

  25.     with ThreadPoolExecutor(max_workers=max_threads) as executor:
  26.         
  27.         label_var = tk.StringVar()
  28.         label_var.set('等待下载...')
  29.         label = tk.Label(window, textvariable=label_var)
  30.         label.pack()

  31.         count = 0
  32.         for download_url in data:
  33.             directory = os.path.join(url_entry.get(), *download_url.split('/')[1:-1])
  34.             count += 1

  35.             if downloading:   
  36.                 executor.submit(download_file, download_url, directory, lock, event, label_var,count)
  37.                
  38.                 # 在等待时,检查下载是否被暂停
  39.                 while not event.is_set():
  40.                     if not downloading:  
  41.                         event.wait()
  42.                         
  43.             else:
  44.                 while not downloading:   
  45.                     event.wait()

  46.         label_var.set('下载完成')
  47.                     
  48. # 定义暂停和恢复函数        
  49. def pause_download():
  50.     global downloading, event

  51.     downloading =False   
  52.     event.clear()   
  53.     messagebox.showinfo('提示', '下载已暂停')
  54.    
  55. def resume_download():
  56.     global downloading, event
  57.    
  58.     if not downloading:
  59.         downloading = True   
  60.         event.set()import logging
  61. import threading
  62. from concurrent.futures import ThreadPoolExecutor
  63. import json
  64. import os
  65. import tkinter as tk
  66. from tkinter import filedialog, messagebox

  67. def download(max_threads):
  68.     # 检查用户是否已经选择了json文件
  69.     if not file_path_var.get():
  70.         messagebox.showinfo('提示', '请先选择json文件')
  71.         return

  72.     # 读取本地json文件


  73.     os.makedirs(url_entry.get()+'/logs', exist_ok=True)
  74.     logging.basicConfig(filename=url_entry.get()+'/logs/download.log', level=logging.ERROR)

  75.     with open(file_path_var. get(), 'r') as f:
  76.         data = json.load(f)

  77.     lock = threading.Lock()
  78.    
  79.     # 创建事件对象
  80.     event = threading.Event()
  81.     event.set()

  82.     # 定义下载标志
  83.     downloading = True

  84.     with ThreadPoolExecutor(max_workers=max_threads) as executor:
  85.         
  86.         label_var = tk.StringVar()
  87.         label_var.set('等待下载...')
  88.         label = tk.Label(window, textvariable=label_var)
  89.         label.pack()

  90.         count = 0
  91.         for download_url in data:
  92.             directory = os.path.join(url_entry.get(), *download_url.split('/')[1:-1])
  93.             count += 1

  94.             if downloading:   
  95.                 executor.submit(download_file, download_url, directory, lock, event, label_var,count)
  96.                
  97.                 # 在等待时,检查下载是否被暂停
  98.                 while not event.is_set():
  99.                     if not downloading:  
  100.                         event.wait()
  101.                         
  102.             else:
  103.                 while not downloading:   
  104.                     event.wait()

  105.         label_var.set('下载完成')
  106.                     
  107. # 定义暂停和恢复函数        
  108. def pause_download():
  109.     global downloading, event

  110.     downloading =False   
  111.     event.clear()   
  112.     messagebox.showinfo('提示', '下载已暂停')
  113.    
  114. def resume_download():
  115.     global downloading, event
  116.    
  117.     if not downloading:
  118.         downloading = True   
  119.         event.set()
复制代码

这样试试?
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2023-5-11 16:07:03 | 显示全部楼层
本帖最后由 isdkz 于 2023-5-11 16:15 编辑

在你的代码中,暂停和恢复的实现有些问题。你的代码是使用 threading.Event() 来控制下载线程的暂停和恢复。你在定义 download_file 函数时将 event 作为参数传入,然后在循环下载时通过 event.is_set() 来判断是否应该暂停下载,但是 event.is_set() 返回 True 时,你的程序会一直下载,直到下载完所有的文件,即使 event 的状态被改变。

然而, requests.get() 并没有提供暂停和恢复的功能,所以这里我们需要使用一个更复杂的方法来实现这个功能。

我们需要对每个下载任务进行细分,将每个下载任务分为多个小块,然后在下载每个小块之前检查 event 的状态,如果 event.is_set() 返回 False ,那么我们就暂停下载。当 event.is_set() 返回 True 时,我们就恢复下载。这样我们就可以实现下载的暂停和恢复。

由于你的程序是使用GUI来控制下载,所以我们还需要把 event 对象作为全局变量,这样我们的暂停和恢复按钮就可以控制 event 的状态了。

我这里给出了一个简化的代码示例来说明这个方法:

  1. import requests
  2. import threading
  3. import os
  4. import time

  5. # 创建 event 对象
  6. event = threading.Event()
  7. event.set()  # 初始状态为 True

  8. def download_file(url, filename):
  9.     # 分块下载的大小
  10.     chunk_size = 1024  # 1 KB
  11.     response = requests.get(url, stream=True)
  12.     # 分块下载文件
  13.     with open(filename, 'wb') as f:
  14.         for chunk in response.iter_content(chunk_size):
  15.             # 检查 event 对象的状态,如果为 False,则暂停下载
  16.             while not event.is_set():
  17.                 time.sleep(1)
  18.             f.write(chunk)

  19. def pause_download():
  20.     event.clear()

  21. def resume_download():
  22.     event.set()

  23. # 开始下载
  24. threading.Thread(target=download_file, args=('https://example.com/file.zip', 'file.zip')).start()

  25. # 暂停下载
  26. time.sleep(5)
  27. pause_download()

  28. # 恢复下载
  29. time.sleep(5)
  30. resume_download()
复制代码


在这个示例中,我们首先创建了一个 event 对象,并且将其初始状态设置为 True 。然后我们定义了一个 download_file 函数来下载文件。在这个函数中,我们将文件分成1KB的块,然后在下载每一块之前都会检查 event 的状态。如果 event.is_set() 返回 False ,那么我们就暂停下载,直到 event.is_set() 返回 True 为止。

我们还定义了 pause_download 和 resume_download 函数来改变 event 的状态。

然后我们启动一个新线程来下载文件。在下载的过程中,我们可以调用 pause_download 和 resume_download 函数来暂停和恢复下载。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2023-5-11 18:06:03 | 显示全部楼层
isdkz 发表于 2023-5-11 16:07
在你的代码中,暂停和恢复的实现有些问题。你的代码是使用 threading.Event() 来控制下载线程的暂停和恢复 ...

整个完整的代码呢。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-5-11 18:07:02 | 显示全部楼层
很抱歉,您提供的代码没有实现暂停和恢复功能,因此无法在其基础上进行修改。需要实现暂停和恢复功能,可以考虑在下载函数中添加一个变量来记录当前程序状态(下载/暂停),然后根据这个状态来判断是否下载文件并改变事件对象的状态。例如:

1. 在 `download` 函数中创建一个变量 `downloading` 来标记当前是否正在下载文件:


  1. def download(max_threads):
  2.     # 检查用户是否已经选择了json文件
  3.     if not file_path_var.get():
  4.         messagebox.showinfo('提示', '请先选择json文件')
  5.         return

  6.     # 读取本地json文件


  7.     os.makedirs(url_entry.get()+'/logs', exist_ok=True)
  8.     logging.basicConfig(filename=url_entry.get()+'/logs/download.log', level=logging.ERROR)

  9.     with open(file_path_var.get(), 'r') as f:
  10.         data = json.load(f)
  11.         
  12.     lock = threading.Lock()
  13.     with ThreadPoolExecutor(max_workers=max_threads) as executor:
  14.         event = threading.Event()
  15.         event.set()

  16.   -->   downloading = True              # 新增:用于表示是否在下载文件
  17.         label_var = tk.StringVar()
  18.         label_var.set('等待下载...')
  19.         label = tk.Label(window, textvariable=label_var)
  20.         label.pack()

  21.         count = 0
  22.         for download_url in data:
  23.             directory = os.path.join(url_entry.get(), *download_url.split('/')[1:-1])
  24.             count += 1


  25.     -->      if downloading:        # 新增:判断是否处于下载状态
  26.                 executor.submit(download_file, download_url, directory, lock, event, label_var,count)
  27.                 while not event.is_set():
  28.                     if not downloading:  # 新增:暂停状态时,将事件等待
  29.                         event.wait()
  30.     -->      else:
  31.                 while not downloading:   # 新增:等待下载状态启动
  32.                     event.wait()

  33.         label_var.set('下载完成')
复制代码


2. 在 `pause_download` 函数中,将 `downloading` 的值设为 False,并将事件对象清除:


  1. def pause_download():
  2.     global downloading

  3.     downloading =False    # 新增:将downloading的值设为False
  4.     event.clear()    # 新增:清除事件对象
  5.     messagebox.showinfo('提示', '下载已暂停')
复制代码


3. 在 `resume_download` 函数中,将 `downloading` 的值设为 True,然后设置事件对象:


  1. def resume_download():
  2.     global downloading
  3.    
  4.     if not downloading:
  5.         downloading = True    # 新增:将downloading的值设为True
  6.         event.set()    # 新增:设置事件对象
  7.         messagebox.showinfo('提示', '下载已恢复')
复制代码

这样实现了控制下载过程的暂停和恢复功能,当点击暂停按钮时,程序会停止向线程池添加新的任务,并且将当前正在执行的任务运行完即可退出。如果希望立即停止所有运行任务,则需要在循环等待事件时添加超时检查,并且将下载函数的子线程改为可以被中断的方式运行。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2023-5-11 18:32:28 | 显示全部楼层
sfqxx 发表于 2023-5-11 18:07
很抱歉,您提供的代码没有实现暂停和恢复功能,因此无法在其基础上进行修改。需要实现暂停和恢复功能,可以 ...

"event" is not definedPylance
..报错
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-5-11 18:42:38 | 显示全部楼层    本楼为最佳答案   
koma610630 发表于 2023-5-11 18:32
"event" is not definedPylance
..报错
  1. import logging
  2. import threading
  3. from concurrent.futures import ThreadPoolExecutor
  4. import json
  5. import os
  6. import tkinter as tk
  7. from tkinter import filedialog, messagebox

  8. def download(max_threads):
  9.     # 检查用户是否已经选择了json文件
  10.     if not file_path_var.get():
  11.         messagebox.showinfo('提示', '请先选择json文件')
  12.         return

  13.     # 读取本地json文件


  14.     os.makedirs(url_entry.get()+'/logs', exist_ok=True)
  15.     logging.basicConfig(filename=url_entry.get()+'/logs/download.log', level=logging.ERROR)

  16.     with open(file_path_var. get(), 'r') as f:
  17.         data = json.load(f)

  18.     lock = threading.Lock()
  19.    
  20.     # 创建事件对象
  21.     event = threading.Event()
  22.     event.set()

  23.     # 定义下载标志
  24.     downloading = True

  25.     with ThreadPoolExecutor(max_workers=max_threads) as executor:
  26.         
  27.         label_var = tk.StringVar()
  28.         label_var.set('等待下载...')
  29.         label = tk.Label(window, textvariable=label_var)
  30.         label.pack()

  31.         count = 0
  32.         for download_url in data:
  33.             directory = os.path.join(url_entry.get(), *download_url.split('/')[1:-1])
  34.             count += 1

  35.             if downloading:   
  36.                 executor.submit(download_file, download_url, directory, lock, event, label_var,count)
  37.                
  38.                 # 在等待时,检查下载是否被暂停
  39.                 while not event.is_set():
  40.                     if not downloading:  
  41.                         event.wait()
  42.                         
  43.             else:
  44.                 while not downloading:   
  45.                     event.wait()

  46.         label_var.set('下载完成')
  47.                     
  48. # 定义暂停和恢复函数        
  49. def pause_download():
  50.     global downloading, event

  51.     downloading =False   
  52.     event.clear()   
  53.     messagebox.showinfo('提示', '下载已暂停')
  54.    
  55. def resume_download():
  56.     global downloading, event
  57.    
  58.     if not downloading:
  59.         downloading = True   
  60.         event.set()import logging
  61. import threading
  62. from concurrent.futures import ThreadPoolExecutor
  63. import json
  64. import os
  65. import tkinter as tk
  66. from tkinter import filedialog, messagebox

  67. def download(max_threads):
  68.     # 检查用户是否已经选择了json文件
  69.     if not file_path_var.get():
  70.         messagebox.showinfo('提示', '请先选择json文件')
  71.         return

  72.     # 读取本地json文件


  73.     os.makedirs(url_entry.get()+'/logs', exist_ok=True)
  74.     logging.basicConfig(filename=url_entry.get()+'/logs/download.log', level=logging.ERROR)

  75.     with open(file_path_var. get(), 'r') as f:
  76.         data = json.load(f)

  77.     lock = threading.Lock()
  78.    
  79.     # 创建事件对象
  80.     event = threading.Event()
  81.     event.set()

  82.     # 定义下载标志
  83.     downloading = True

  84.     with ThreadPoolExecutor(max_workers=max_threads) as executor:
  85.         
  86.         label_var = tk.StringVar()
  87.         label_var.set('等待下载...')
  88.         label = tk.Label(window, textvariable=label_var)
  89.         label.pack()

  90.         count = 0
  91.         for download_url in data:
  92.             directory = os.path.join(url_entry.get(), *download_url.split('/')[1:-1])
  93.             count += 1

  94.             if downloading:   
  95.                 executor.submit(download_file, download_url, directory, lock, event, label_var,count)
  96.                
  97.                 # 在等待时,检查下载是否被暂停
  98.                 while not event.is_set():
  99.                     if not downloading:  
  100.                         event.wait()
  101.                         
  102.             else:
  103.                 while not downloading:   
  104.                     event.wait()

  105.         label_var.set('下载完成')
  106.                     
  107. # 定义暂停和恢复函数        
  108. def pause_download():
  109.     global downloading, event

  110.     downloading =False   
  111.     event.clear()   
  112.     messagebox.showinfo('提示', '下载已暂停')
  113.    
  114. def resume_download():
  115.     global downloading, event
  116.    
  117.     if not downloading:
  118.         downloading = True   
  119.         event.set()
复制代码

这样试试?
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2023-5-11 19:02:51 | 显示全部楼层

多谢了,  我去看看
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-6-3 08:29

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表