下面的代码可以使用vba来并发完成吗?

这段代码用一个模板,产生2025年每天的日报表备用。

Sub saleReport()
    t1 = Timer()
    Dim wb As Workbook
    Dim ws As Worksheet
    Dim templatePath As String
    Dim oYear, oMonth, DaysInMonth As Integer
    templatePath = "d:\templateSale.xlsx"
    Set wb = Workbooks.Open(templatePath)
    Set ws = wb.Sheets(1)
    oYera = 2025
    For oMonth = 1 To 12
        DaysInMonth = Day(DateSerial(oYear, oMonth + 1, 1) - 1)
        For oDay = 1 To DaysInMonth
            fn = "2025年" + CStr(oMonth) + "月" + CStr(oDay) + "日的销售报表"
            newFilePath = "d:\" + fn + ".xlsx"
            ws.Name = fn
            wb.SaveAs Filename:=newFilePath, FileFormat:=xlOpenXMLWorkbook
        Next oDay
    Next oMonth
    wb.Close SaveChanges:=False
    Set wb = Nothing
    Set ws = Nothing
    t2 = Timer()
    Debug.Print t2 - t1
End Sub

程序运行了23秒。有无办法使用vba来并行调用我的4核cpu?将速度降低在几秒内?

阅读 4k
avatarAI BotBETA

答案

VBA 本身并不直接支持多线程或并发处理。然而,你可以通过一些方法间接实现并行处理,比如使用 Excel 的 Application.OnTime 方法进行任务调度,或者借助外部库如 Windows API 调用。不过,对于 VBA 来说,最常见和相对简单的方法是通过将任务拆分成多个独立的 VBA 宏,并在不同的 Excel 实例中运行这些宏来实现并行处理。

以下是一个概念性的解决方案,涉及使用 Windows 脚本宿主(WSH)和批处理文件来启动多个 Excel 实例,每个实例运行一个修改过的 VBA 宏来处理部分月份的数据。

  1. 拆分 VBA 宏:将原始宏拆分成多个宏,每个宏处理几个月的数据。
  2. 创建批处理文件:编写一个批处理文件,使用 start 命令启动多个 Excel 实例,每个实例打开一个包含相应宏的工作簿并运行它。
  3. 合并结果:在所有宏运行完成后,如果需要,可以编写一个额外的宏或脚本来合并结果。

请注意,这种方法可能需要一些额外的编程工作,并且处理多个 Excel 实例可能会增加内存使用。此外,由于 VBA 和 Excel 的单线程性质,这种方法可能不会完全利用多核 CPU 的所有潜力,但它可以显著减少总运行时间,特别是当处理大量数据时。

对于你的具体代码,你需要将其拆分成多个部分,每个部分处理几个月的数据,并在不同的 Excel 实例中运行。这种方法可以显著减少运行时间,但具体减少多少取决于多种因素,包括系统性能、任务量以及任务之间的依赖性。

如果你不熟悉批处理文件和 Windows 脚本宿主,可能需要一些时间来学习和实现这种方法。另外,请确保在尝试并行处理时考虑到 Excel 的资源限制和潜在的冲突。

3 个回答

Python或者c++这种多线程的语言试试,
用 Python multiprocessing 模块并发:

import openpyxl
from multiprocessing import Pool
import os
import datetime

def create_daily_report(date_tuple):
    year, month, day = date_tuple
    template_path = "d:\\templateSale.xlsx"
    new_file_name = f"{year}年{month}月{day}日的销售报表.xlsx"
    new_file_path = os.path.join("d:\\", new_file_name)

    workbook = openpyxl.load_workbook(template_path)
    worksheet = workbook.active
    worksheet.title = new_file_name
    # ... 根据日期修改工作表 ...

    workbook.save(new_file_path)
    workbook.close()
    print(f"已生成:{new_file_path}")

if __name__ == "__main__":
    year = 2025
    dates = []
    for month in range(1, 13):
        days_in_month = (datetime.date(year, month + 1, 1) - datetime.timedelta(days=1)).day if month < 12 else 31
        for day in range(1, days_in_month + 1):
            dates.append((year, month, day))

    # 4个进程的进程池
    with Pool(processes=4) as pool:
        pool.map(create_daily_report, dates)

    print("所有报表生成完毕!")

你这样的场景,可以使用多线程方式解决:

' 引入必要的Windows API函数声明
Private Declare PtrSafe Function CreateThread Lib "kernel32" ( _
    ByVal lpThreadAttributes As LongPtr, _
    ByVal dwStackSize As Long, _
    ByVal lpStartAddress As LongPtr, _
    lpParameter As Any, _
    ByVal dwCreationFlags As Long, _
    lpThreadId As LongPtr _
) As LongPtr

Private Declare PtrSafe Function CloseHandle Lib "kernel32" ( _
    ByVal hObject As LongPtr _
) As Long

' 定义一个自定义数据类型,用于传递参数给线程函数
Type ThreadParams
    TemplatePath As String
    YearValue As Integer
    MonthValue As Integer
    DayValue As Integer
    NewFilePath As String
End Type
新手上路,请多包涵

Sub saleReport()

Dim t1 As Double, t2 As Double
t1 = Timer()

Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False

Dim wb As Workbook
Dim ws As Worksheet
Dim templatePath As String
Dim oYear As Integer, oMonth As Integer, DaysInMonth As Integer
Dim oDay As Integer
Dim fn As String, newFilePath As String

templatePath = "d:\templateSale.xlsx"
Set wb = Workbooks.Open(templatePath)
Set ws = wb.Sheets(1)
oYear = 2025

For oMonth = 1 To 12
    DaysInMonth = Day(DateSerial(oYear, oMonth + 1, 1) - 1)
    For oDay = 1 To DaysInMonth
        fn = "2025年" & CStr(oMonth) & "月" & CStr(oDay) & "日的销售报表"
        newFilePath = "d:\" & fn & ".xlsx"
        ws.Name = fn
        wb.SaveAs Filename:=newFilePath, FileFormat:=xlOpenXMLWorkbook
    Next oDay
Next oMonth

wb.Close SaveChanges:=False
Set wb = Nothing
Set ws = Nothing

Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True

t2 = Timer()
Debug.Print "运行时间:" & t2 - t1 & "秒"

End Sub

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题