代码执行明显延迟-有什么提示吗?

ConsKa

知名会员
已加入
2020年12月11日
留言内容
73
编程经验
Beginner
单击一个按钮,我会检查是否打开了一个excel,是否弹出一条消息将其关闭-一旦关闭,就会调用以下代码:

1. Record all excel process IDs in a 哈希表;
2.将数据导出到excel电子表格;
3. Kill any excel processes that do not exist in the 哈希表.

在将数据导出到excel电子表格的开始,我有以下代码:

C#:
OutPut.Text = "WORKING";
OutPut.BackColor = Color.DarkGreen;

这种情况告诉我代码已经到了这一点,因为按钮几乎立即更改了颜色,并且文本从“创建输出”更改为“工作”。

但是,如果您打开了电子表格,并要求您将其关闭。 -关闭它之后...更改按钮和打开对话框之间的时间大约是10秒....如果单击输出时未打开Excel,则该时间大约是1纳秒。

下一行很简单:

C#:
if (File.Exists(ctk1.EX1))
{
    DialogResult dialogResult = MessageBox.Show
($"This will delete { ctk1.EX1 } are you sure?", "Delete Excel", MessageBoxButtons.YesNo);

这是一个对话框,似乎需要一段时间才能显示。

我尝试了一个断点,看它是否打算去做某事....但是不是,它只是挂了很长时间。

有什么技巧可以弄清楚为什么会有这样的延迟,这可能会导致解决问题?

下面是整个IF语句,以防万一我嵌套它的方式引起了问题。它是第一个DialogResult很慢-一旦触发,其余的就很好,没有延迟。

C#:
if (OutPut.Text == "CREATE OUTPUT")
            {
                OutPut.Text = "WORKING";
                OutPut.BackColor = Color.DarkGreen;

                if (File.Exists(ctk1.EX1))
                {
                    DialogResult dialogResult = MessageBox.Show($"This will delete { ctk1.EX1 }, are you sure?", "Delete Excel", MessageBoxButtons.YesNo);
                    if (dialogResult == DialogResult.Yes)
                    {
                        File.Delete(ctk1.EX1);
                        MessageBox.Show("Daily Log Deleted");
                    }
                    else if (dialogResult == DialogResult.No)
                    {
                        OutPut.Text = "CREATE OUTPUT";
                        OutPut.BackColor = Color.Maroon;
                        return;
                    }
                }
                else
                {
                }
 

ConsKa

知名会员
已加入
2020年12月11日
留言内容
73
编程经验
Beginner
好吧,将其分为两种方法(检查它是否存在,如果得到用户同意,则删除),然后调用export方法,而不是在一种方法中做两件事-我知道我不应该这样做。无论电子表格是否打开,都不会延迟。

但是,如果有人对延迟的原因有任何建议,还是想听听。
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,465
位置
弗吉尼亚州切萨皮克
编程经验
10+
从我的角度来看,有两种可能性:
1.您的计算机没有能力同时处理Excel,Visual Studio和您的应用程序。
2.通过在代码中的某个位置使用Application.DoEvents(),使代码意外重入。

为什么需要Excel进程ID的哈希表?进程ID列表不足够吗?

您如何将数据导出到Excel?您是否正在使用Excel自动化对象模型?您正在使用第三方图书馆吗?

您到底如何终止新的Excel流程?您是让它优雅地关闭,还是只是调用Process.Kill()而不是Process.CloseMainWindow()?

您的流程的重要性是什么?机器上Office安装的位数是多少?
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,465
位置
弗吉尼亚州切萨皮克
编程经验
10+
我刚刚看到了您的帖子#2。让我更倾向于重入代码。
 

ConsKa

知名会员
已加入
2020年12月11日
留言内容
73
编程经验
Beginner
用于ID的哈希表,这样我就可以杀死创建的excel进程而又不杀死其他任何打开的excel...。这是因为它始终将自己的版本保留在任务管理器中。

Microsoft Interop用于处理excel。

我认为我的Ryzen 9 3900x并不是问题所在-但这需要在笔记本电脑上运行,因此请切记。到目前为止,它确实没有繁重的过程。

只是简单的ExcelProcess.Kill();,它就挂在了衣架上,应该在excel完全关闭后关闭,但是它没有....所以您必须杀死它,否则最终可能会像其中的20个一样在任务管理器中,它可能会使您感到困惑。

"我刚刚看到了您的帖子#2。让我更倾向于重入代码。"

显然,我不知道,只是将代码移至另一种方法-除了添加对ExportDataToExcel()的调用之外,我没有做任何更改。现在我上面没有测试/删除IF语句。我的代码中也没有任何while循环。

我考虑了一个ExcelOpen();测试-但我只是做了一个IF,然后再次调用该方法ExcelOpen(); -我确实想修复该问题,但它通常是我第一步...开始工作,然后做得更好。
 

约翰·H

C#论坛主持人
工作人员
已加入
2011年4月23日
留言内容
1,009
位置
挪威
编程经验
10+
看来您正在尝试解决经典问题"memory release"Office互操作使用肮脏方法的问题。通常,如果您在代码中调用Application.Quit,则COM对象将持续存在直到下一个GC,这可能需要一段时间,它会在应用程序关闭后被释放-除非您的互操作代码崩溃。

我在此线程中找到了一种简单的清洁方法: 退出后应用程序未退出 -这是方法:
C#:
private void CleanupExcel()
{
    do
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
    while (System.Runtime.InteropServices.Marshal.AreComObjectsAvailableForCleanup());
}
当我对此进行测试时,仅花费了几毫秒的时间并清理了一个COM对象的循环,大约1-2秒后,Excel实例也在任务管理器中消失了。
重要的是调用Application.Quit,并尝试捕获互操作代码以确保执行此操作。在执行互操作的方法返回后,还需要调用CleanupExcel方法。例如,您可以这样做:
C#:
private async void button1_Click(object sender, EventArgs e)
{
    //could check/delete target file first
    await Task.Run(Excelworks);
    _ = Task.Run(CleanupExcel);
}

private void Excelworks()
{
    var app = new Excel.Application();
    app.Visible = false;
    try
    {     
        //do interop, save and close book
        var doc = app.Workbooks.Add();
        var sheet = (Excel.Worksheet)doc.ActiveSheet;
        sheet.Range["A1"].Value = "a value";
        var pathOut = System.IO.Path.Combine(Application.StartupPath, "output.xlsx");
        sheet.SaveAs(pathOut);
        doc.Close(false);
    }
    catch (Exception)
    {
        //nothing to do
    }
    app.Quit();
}
I just included some basic interop code in sample to show that no special 内存释放 is used here, I'm referring to the endless calls to ReleaseComObject/set null/GC.Collect for any and all interop objects used that you probably have seen around the webs.
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,465
位置
弗吉尼亚州切萨皮克
编程经验
10+
用于ID的哈希表,这样我就可以杀死创建的excel进程而又不杀死其他任何打开的excel...。这是因为它始终将自己的版本保留在任务管理器中。
我不明白因此,现在您不仅要掌握所有当前正在运行的Excel流程的ID,而且还要掌握代表这些Excel流程的所有Process对象实例。这些对象并不便宜。进程ID只是一个整数,很便宜。
 

ConsKa

知名会员
已加入
2020年12月11日
留言内容
73
编程经验
Beginner
我不明白因此,现在您不仅要掌握所有当前正在运行的Excel流程的ID,而且还要掌握代表这些Excel流程的所有Process对象实例。这些对象并不便宜。进程ID只是一个整数,很便宜。
I am creating a 哈希表, I thought that would just be a table of hashes...not the process IDs and not process object instances?

那不对吗?我认为这只是为每个进程存储和比较唯一ID的便捷方法,而不是创建两个要比较的列表。
 

ConsKa

知名会员
已加入
2020年12月11日
留言内容
73
编程经验
Beginner
看来您正在尝试解决经典问题"memory release"Office互操作使用肮脏方法的问题。通常,如果您在代码中调用Application.Quit,则COM对象将持续存在直到下一个GC,这可能需要一段时间,它会在应用程序关闭后被释放-除非您的互操作代码崩溃。

我在此线程中找到了一种简单的清洁方法: 退出后应用程序未退出 -这是方法:
谢谢,我已经看过几次引用这种清理方法的方法,但是对于特定的任务,它总是用非常具体的术语表示-我上次看并不是真正“懂”的东西。

不过,我将仔细阅读您的解释,该解释看起来更加清晰,看看我是否可以更改代码并获得相同的结果。
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,465
位置
弗吉尼亚州切萨皮克
编程经验
10+
I guess I misunderstood. In C# , a 哈希表 is a Dictionary<TKey,TValue>. With such a situation, presumably the key would be the process ID, and the value would be the process object.

但是如果你使用这个词"hashtable" in the traditional way computer science classes teach how 哈希表s work they focus just on the key and never discuss the value, then yes that would be less expensive than holding on to the Process objects.

我强烈建议上述有关使Excel正常终止的方法。如果您真的想终止Excel流程,请考虑使用LINQ 除() to determine which ID is the new ID if you have a before and after list of IDs. And once you have that ID, do not use Process.Kill(). Use Process.CloseMainWindow() and only fallback on the Process.Kill() if the process still doesn't terminate after 2-3 minutes.
 

ConsKa

知名会员
已加入
2020年12月11日
留言内容
73
编程经验
Beginner
I guess I misunderstood. In C# , a 哈希表 is a Dictionary<TKey,TValue>. With such a situation, presumably the key would be the process ID, and the value would be the process object.

但是如果你使用这个词"hashtable" in the traditional way computer science classes teach how 哈希表s work they focus just on the key and never discuss the value, then yes that would be less expensive than holding on to the Process objects.

我强烈建议上述有关使Excel正常终止的方法。如果您真的想终止Excel流程,请考虑使用LINQ 除() to determine which ID is the new ID if you have a before and after list of IDs. And once you have that ID, do not use Process.Kill(). Use Process.CloseMainWindow() and only fallback on the Process.Kill() if the process still doesn't terminate after 2-3 minutes.
我可以问一下,仅终止进程有什么问题?为什么这么糟?

它会导致什么问题,或者仅仅是丑陋的代码?

我让excel正常退出的问题是,如果我尝试打开文件以检查是否已写入,则我的衣架已出现问题。

如果我终止了该进程,所有这些问题将立即消失,这对用户很有用,因为他们不会知道需要清除时间的com对象?

诚然,我已经使用了一段时间了,也许挂架问题与我在早期进行更多调试时有关,并且会遇到问题,并会在挂架打开时崩溃....不确定为什么立即摆脱它不是一件好事。
 

ConsKa

知名会员
已加入
2020年12月11日
留言内容
73
编程经验
Beginner
我为Linq写了一些代码-我认为它与我的冗长代码具有相同的作用...

他们走了,花了一分钟,但是走了。...阅读讨论之后,如果有人想要我的代码-这取代了19行代码,所以大声喊着....仍然会想知道为什么我不该杀那该死的东西 :)

C#:
//Public variables
Process[] AllProcesses;
Process[] AllProcesses2;

// Called prior to running any interop code
AllProcesses = Process.GetProcessesByName("excel");

// Called after running the interop code
AllProcesses2 = Process.GetProcessesByName("excel");

IEnumerable<Process> createdExcel = AllProcesses2.Except(AllProcesses);

foreach (Process item in createdExcel)
    item.CloseMainWindow();
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,465
位置
弗吉尼亚州切萨皮克
编程经验
10+
为什么您不只是终止一个进程有多种原因。

For starters, you are not giving the application a chance to shutdown and clean up anything it needs to clean up, or flush any files that it needs to write back out. Recall how in C++ it is a big no-no to exit() in the middle of a program? Do you recall the reason why? Well the reason is that system call just stops a program dead in its tracks. It doesn't give the program a chance to call any of its clean up code in its destructors. So basically the same thing happens when you just call Process.Kill(). You just stop a program dead in its tracks and don't let it do any of it's normal clean up and shutdown. Imagine if Excel were running and it happened to be in the middle of manipulating an Access database. Since the Access database engine runs in the same process space as Excel, by killing Excel, you are introducing a probability of corrupting the database -- not that Access needs any extra help corrupting its databases, it used to do that on its own quite well before the Office 2007 version :)。即使Excel未使用数据库也是如此。使用现代的.xlsx文件格式,进入.xlsx文件的内部XML数据需要压缩并写出到文件中。如果在杀死Excel时Excel仍在忙于清除该文件数据,则可能会导致文件损坏。

另一个原因是您要拆除COM / OLE远程通信路径的一部分。正如您在创建Excel自动化对象时发现的那样,Excel实际上是在一个单独的进程中运行,但是您正在从进程中的代码调用Excel。 Kraig Brockschmidt在他的书中对COM / OLE远程处理的工作原理有更全面的解释 内部OLE,但是基本上发生的是Excel启动并公开了一个或多个OLE对象。该OLE对象已在操作系统的ROT(运行对象表)中注册。在您的代码中,创建具有与Excel OLE对象相同的接口的代理。然后,通过COM / OLE设置一个通信通道,以便在您的进程中运行的代理对象可以与在Excel进程中运行的实际对象进行通信。由于COM / OLE是在Win3.x时代创建的,因此TCP / IP并不常见,因此Microsoft避免使用命名管道,因为各种带有命名管道的Win3.x fiascos和共享内存被认为太危险了,这是两者之间的通信方式。通过使用Windows消息完成处理。 (这是我担心重新进入的部分原因,因为如果您弄乱了消息泵(最常见的情况是在错误的位置意外地调用了Application.DoEvents(),或者罕见地通过异常的窗口或对话框消息处理) ,您还弄乱了所有COM / OLE通信,到那儿去了。我有CairOLE T恤,还欠COM / OLE朋友一些晚餐,因为他们帮助我调试了为什么我继续努力"COM Server Busy"问题。)因此,当您只是终止Excel进程时,Excel就没有机会从ROT中将其自身删除,因此COM / OLE继续尝试发送消息,并期望从现在已死的Excel COM对象中返回消息。 )。

另一个原因是遥测并破坏了一个或多个Microsoft工程师的日,周或月。如果您选择使用Office遥测,则Office将报告Excel看起来异常终止的次数。如果您选择使用Windows遥测,则Windows还将报告哪些应用程序似乎异常终止以及多少次终止。微软获取的数据是匿名的并且是分类存储的,因此查看传入数据的工程师将无法确定该问题似乎仅与一台特定的计算机或用户有关。他们所看到的只是计数,有关OS版本的一般信息,硬件类型等。如果发生崩溃/异常终止的频率足够高,则必须调查可能发生的情况。从过去的经验来看,这是一个麻木的任务。如果工程师很幸运,他们将拥有一个调用栈,但是对于您而言,由于您刚刚终止了该应用程序,因此变得像无限艰难,因此不会生成任何调用栈。
 

ConsKa

知名会员
已加入
2020年12月11日
留言内容
73
编程经验
Beginner
谢谢Skydiver,一个很好的解释-我之所以遵循您的建议(如上面的文章所示),仅仅是因为我知道何时遵循该建议-但能很好地解释一个原因是很不错的,而不是一味地跟随。

虽然现在您只是诱使我在我认识的每个用户中都对excel实施了对Excel的Kill.Process() :)
 
最佳 底部