为什么并行化代码不会写入Excel电子表格?
在为什么并行化代码不会写入Excel电子表格?,excel,parallel-processing,f#,excel-interop,Excel,Parallel Processing,F#,Excel Interop,在Excel电子表格中编写许多工作表可能需要一段时间。将其并行化会有所帮助 此代码运行良好,它使一个Excel电子表格在屏幕上弹出,其中有四个工作表,分别名为Sheet1、1、2和3 open Microsoft.Office.Interop.Excel open FSharp.Collections.ParallelSeq let backtestWorksheets = [1..3] let app = new ApplicationClass(Visible = true) let
Excel
电子表格中编写许多工作表可能需要一段时间。将其并行化会有所帮助
此代码运行良好,它使一个Excel
电子表格在屏幕上弹出,其中有四个工作表,分别名为Sheet1
、1
、2
和3
open Microsoft.Office.Interop.Excel
open FSharp.Collections.ParallelSeq
let backtestWorksheets = [1..3]
let app = new ApplicationClass(Visible = true)
let workbook = app.Workbooks.Add(XlWBATemplate.xlWBATWorksheet)
let writeInfoSheet (worksheet: Worksheet) : unit =
let foo i =
let si = string i
worksheet.Range("A" + si, "A" + si).Value2 <- "Hello " + si
List.iter foo [1..10]
let wfm = [1, writeInfoSheet; 2, writeInfoSheet; 3, writeInfoSheet]
|> Map.ofList
let adder (workbook : Workbook)
(i : int)
: unit =
let sheet = workbook.Worksheets.Add() :?> Worksheet
sheet.Name <- string i
wfm.[i] sheet
List.iter (adder workbook) backtestWorksheets
//PSeq.iter (adder workbook) backtestWorksheets
[<EntryPoint>]
let main argv =
printfn "%A" argv
0 // return an integer exit code
如果用List.iter
替换PSeq.iter
,则不会发生这种情况
我无法在一个足够简单的上下文中复制此异常,使其成为一个适当的SO问题,但我仍然对处理此异常的任何建议感兴趣。看起来Microsoft.Office.Interop.Excel代码从来没有设计为一次从多个线程调用。在MS Office论坛中,关于在多线程中执行更新(在C#中)。我将在这里引用该答案的相关部分: 使用多线程在多个工作表中搜索最终会使用Excel的核心—Excel.Application对象,这意味着线程需要排队才能一次运行一个,从而剥夺了应用程序所需的性能改进 [……] 所有这些都是因为Office对象模型不是线程安全的 如果要调用
Microsoft.Office.Interop
命名空间中的任何内容,看起来您必须使用非并行设计
编辑:在评论中提出了一个很好的建议:在多个线程上完成所有后台工作,并使用
MailboxProcessor
对电子表格进行实际更新。MailboxProcessor的消息队列将自动为您序列化更新操作,而无需您额外的工作。也许您可以同时编写单独的工作表,然后再合并它们?即使有办法让它同时工作在同一个文件中,我也不推荐它。(至于“为什么”它不受支持,这是一个你必须问微软的广泛问题。)我对F#一无所知,所以我不知道你将如何实现这一点,但“消息过滤器表明应用程序正忙”的解决方案是实现你自己的消息过滤器。请参阅:。请注意,运行筛选器的要求是STA线程。您可以在多个线程上执行所有后台工作,并使用MailboxProcessor
序列化电子表格的实际更新。我不确定Microsoft.Office.Interop.Excel
不能与并行化代码一起使用。该工作簿大约有20个工作表,其中只有三个在从PSeq.iter
创建时生成异常。他们是唯一拥有数千个数据点图表的公司。因此,塔尔创建它们的代码必须是连续的(List.iter
)。其他的被称为PSeq.iter
。这样做可以节省大约10秒的时间(40秒->30秒)(我测试了从List.iter
切换到PSeq.iter
,在可以并行多次的组上,节省的时间是一致的)。@Soldalma-“非线程安全”这并不意味着同时从多个线程运行代码总是会失败,它只是意味着不能保证总是成功。事实上,到目前为止,在较短的工作表上,它对您的效果很好,但这并不意味着它将始终有效。您可能会遇到这样的情况:其他地方的CPU或I/O负载过大,导致较短的工作表线程在通常成功的情况下失败,因为线程的计时方式发生了无法预测或控制的变化。序列化所有内容更安全,IMHO。@rmunn-谢谢。我设法将时间从40秒减少到7秒,因为我每天都要跑这么多次,这是一个显著的区别。我是这段代码的唯一用户,所以我会一直这样做,直到出现问题,然后通过序列化罪犯工作表来解决问题。@rmun-Aaron M.Eshbach的建议是个好主意,但看起来大部分时间都花在了电子表格的实际更新上,无论如何都会序列化。
Unhandled Exception: System.TypeInitializationException: The type initializer for '<StartupCode$Fractal13>.$Program' threw an exception. ---> System.AggregateException: One or more errors occurred. ---> System.Runtime.InteropServices.COMException: The message filter indicated that the application is busy. (Exception from HRESULT: 0x8001010A (RPC_E_SERVERCALL_RETRYLATER))