C# 如何高效地使用 Process.Start()

3
下面是一段代码,这段代码将两个文件合并以生成另一个文件。
foreach (string jpegfile in files)
{
    string fileName = jpegfile + ".dcm";
    string ConfigFile =GetFileNameWithoutExtension(jpegfile) + ".cfg";
    string destFile = System.IO.Path.Combine(DirectoryPath, fileName);
    string command = "/C " + batFileName + " -C " + patientConfigFile + " " + jpegfile + " " + destFile;
    try
    {
        System.Diagnostics.Process process = new System.Diagnostics.Process();
        process.StartInfo.UseShellExecute = false;
        // You can start any process, HelloWorld is a do-nothing example.
        process.StartInfo.FileName = "cmd.exe";
        process.StartInfo.Arguments = command;
        process.StartInfo.CreateNoWindow = true;


        process.Start();

        process.WaitForExit();

        this.counter++;

        // Report progress as a percentage of the total task.
        int percentComplete =
        (int)((float)this.counter / (float)form.sumofImages * 100);
        if (percentComplete > highestPercentageReached)
        {
            highestPercentageReached = percentComplete;
            worker.ReportProgress(percentComplete);
        }

     }
     catch (Exception exp)
     {
         MessageBoxButtons buttons = MessageBoxButtons.OK;
         DialogResult result;
         result = MessageBox.Show("Batch File execution error " + exp, "Warning", buttons);
     }
}

我的问题是,如果我在start()之后使用waitforexit(),我的代码会花费相当长的时间。如何加快上述循环的速度?

1
我投票关闭此问题,因为它可能更适合于 Code Review SE! - Am_I_Helpful
1
在异步方法中执行这个操作是一个选项吗? - EJoshuaS - Stand with Ukraine
1
除非你想让代码在生成的进程完成其任务之前继续运行,否则你无法加速它。很可能你需要加速正在生成的程序,或者根本无能为力。有些事情需要时间,你无法避免这一点。 - Quantic
2
你应该修改启动的进程来处理批处理本身,而不是为每个图像生成一个新的进程。 - johnny 5
1
你必须等待每个文件处理完成才能处理下一个吗?如果不需要,你可以在循环启动所有进程之后再等待。 - Matt Burland
显示剩余2条评论
2个回答

5
如果您不必等待前一个文件完成后才能开始下一个文件,则可以尝试以下操作:
// for each file, spawn a task to do your processing
var tasks = (from jpegfile in files
            select Task.Run(() =>
            {
                 // set up your process here...
                 process.Start();
                 // task won't be done until process exits
                 process.WaitForExit();
            }).ToArray();
// Wait for all the tasks to be done
Task.WaitAll(tasks);

1
你可以生成一堆任务并使用Task.WaitAll,如Matt Burland的回答所示。
另外还有几个选项,如下所示(我没有对这些进行过太多测试,所以你可能需要自己测试)。首先,你可以使用事件而不是WaitForExit:
private int counter = 0;
    int sumOfImages = 10; // Set this to the number of files

    private void ProcessStart(List<string> files)
    {
        foreach (string file in files)
        {
            Process process = new Process();
            process.StartInfo.UseShellExecute = false;
            // You can start any process, HelloWorld is a do-nothing example.
            process.StartInfo.FileName = "cmd.exe";
            process.StartInfo.Arguments = "someCommand";
            process.StartInfo.CreateNoWindow = true;
            process.EnableRaisingEvents = true;
            process.Exited += Process_Exited;

            process.Start();
        }
    }

    private void Process_Exited(object sender, EventArgs e)
    {
        int result = Interlocked.Increment(ref counter);

        int percentComplete = ((result / sumOfImages) * 100);

        worker.ReportProgress(percentComplete);
    }

如果你愿意,可以把整个过程都放在线程池中。我认为这是一个好的答案——CPU绑定部分是创建和启动进程,将其放在后台线程中,以便不会挂起用户界面。它将消除等待结果的开销,这不是CPU限制的问题。
这里有一个例子。假设你和其他10个人去餐厅吃饭。当服务员来时,10个人中有9个已经准备好点餐了。服务员碰巧先问那个人点什么。此时,桌子上的每个人都可以等他决定,或者服务员可以先从其他9个人那里取订单,然后再回来询问第一个人。很可能增加一个额外的服务员来等待第一个人的订单,在原来的服务员取其他9个订单的时候是毫无意义的。如果绝对必要,服务员可以把9个订单带到厨房,然后再回来拿第一个人的订单。
重点是,如果只是等待一个人的结果,并不一定会给你带来性能提升,增加额外的服务员也没有多大意义。
显然,在此类比中,服务员是一个线程,人们是需要完成的任务。在上述解决方案中,您有一个单独的服务员(线程池线程)为所有顾客提供服务(创建所有进程),然后人们(进程)告诉他们何时准备好点餐(即进程引发事件)。然后,他告诉厨房他们的订单(在工作者上引发ReportProgress事件)。
另一个选项是Parallel.ForEach循环:
private void ProcessStart(List<string> files)
    {
        int sumOfImages = files.Count;
        int count = 0;
        string command = "";

        Parallel.ForEach(files,
            delegate (string file)
            {
                Process process = new Process();
                process.StartInfo.UseShellExecute = false;
                // You can start any process, HelloWorld is a do-nothing example.
                process.StartInfo.FileName = "cmd.exe";
                process.StartInfo.Arguments = command;
                process.StartInfo.CreateNoWindow = true;

                process.Start();

                process.WaitForExit();

                int result = Interlocked.Increment(ref count);

                int percentComplete = ((result / sumOfImages) * 100);

                worker.ReportProgress(percentComplete);
            });
    }

Parallel.forEach() 做到了神奇的效果,我看到了在 CPU 使用率和时间方面大约 25-30% 的提升。 - Quant
@Quant,很高兴听到它有帮助。如果它有助于解决问题,请考虑将其标记为答案。 - EJoshuaS - Stand with Ukraine
你也可以用“file =>”来替换“delegate (string file)” - PRMan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接