【问题标题】:Issues with semaphore and Multi-Threading a Process.Start信号量和多线程 Process.Start 的问题
【发布时间】:2012-10-29 23:33:44
【问题描述】:

不确定我下面的代码有什么问题,但没有按预期工作。设计是允许用户从 ListBox 中选择任意数量的输入文件,然后单击“执行”按钮以使用不同的输入文件启动相同的可执行文件。我将添加一个控件以允许用户指定并发进程的数量,但现在我将硬编码为 3 以进行测试。我的期望是每个可执行文件将按顺序开始(尽管我知道它们可能不会以这种方式完成)并且一次只会执行 3 个(尽管这个数字最终将由用户输入控件控制)。我想为调用进程的函数增加额外的功能,即任务优先级,返回退出代码,但现在我无法让基本功能正常工作。我已经尝试了在这个网站和网络上的其他网站上找到的许多代码变体,但仍然无法让它工作。这是当前版本:

    private void btnParserExe_Click(object sender, EventArgs e)
    {
        Pool = new Semaphore(3,3);  //Pool above declared as class variable 
        string ExeName = "C:\\Program Files (x86)\\Norman\\bin\\OC2.exe";
        string Args;
        string ArgDir = this.dirListBox1.Path + "\\";

        for (int i = 0; i < this.fileListBox1.Items.Count; i++)
        {
            if (this.fileListBox1.GetSelected(i) == true)
            {
                Args = "-i " + this.fileListBox1.get_Items(i) + " -r -c -noerr";
                Thread thread = new Thread(() => DoWork(ArgDir, ExeName, Args, "3"));
                thread.Start();
            }
        }
    }


    private static void DoWork(string WorkingDir, string exefile, string parameters, string priority)
    {
        Pool.WaitOne();

        Process exeProcess = new Process();
        int exitCode;

        try
        {
            exeProcess.StartInfo.FileName = exefile;
            exeProcess.StartInfo.Arguments = parameters;
            exeProcess.StartInfo.WorkingDirectory = WorkingDir;
            exeProcess.Start();
            exeProcess.WaitForExit();
        }
        catch (Exception ex)
        {
            MessageBox.Show("ERROR EXECUTING: " + parameters + " " + ex.Message);
        }
        finally
        {
            exitCode = exeProcess.ExitCode;
        }
        Pool.Release();
    }

}

我遇到的三个主要问题:

  1. 作业并不总是以正确的顺序开始(ListBox 中较低的一些项目在其他较高的项目之前启动)
  2. 有时启动的进程似乎停止了,好像它已经完成但窗口仍然在屏幕上。这是一个非常一致的问题,但并不总是相同的任务/输入文件会停止。有时,在先前的测试中运行良好的会停止运行,反之亦然。
  3. 我的笔记本电脑在测试这部分代码时重新启动??

任何反馈、链接、示例将不胜感激。

【问题讨论】:

  • 这些工作进程是否重叠?例如,三个进程在运行时会相互踩踏吗?如果没有,那么让三个(或 n 个)线程访问处理锁定的单个 GetNextWorkItem() 方法可能是确保所有步骤按顺序开始的更简单方法。
  • 不,这些过程完全独立于另一个。我将研究 GetNextWorkItem()。谢谢

标签: c# multithreading semaphore


【解决方案1】:
  1. 无法保证线程实际启动的顺序。两个线程快速连续启动(在Thread.Start() 意义上)很有可能会遇到“第二个”线程实际上在“第一个”之前开始执行。如果有序执行很重要,您将需要序列化它们的执行(这似乎避免了启动多个线程的需要。)

  2. 我很好奇您是否确定所有进程都以正确的输入文件名启动。在我看来,您的委托使用情况实际上可能最终导致两个使用同一个文件启动,我想知道这是否会导致您的第二个问题。

  3. 多久一次?您的计算机此时是否在执行其他操作?您是否收到任何消息、蓝屏死机或任何形式的信息?这无论如何都不正常。

【讨论】:

  • 1.我应该考虑使用多线程并确保顺序正确? 2. 我对输入文件有信心。我现在只测试 1 组(大约 12 个文件),我最初编写了不带线程的功能,并且进程按预期运行。但是,如果我需要添加一些内容以确保相同的 1 不会被通过两次,请告知。 3. 并非每次测试都会出现这种情况,但它是一台相当新的笔记本电脑,我只是在测试这段代码时遇到了问题。我想如果我能解决问题 1 和 2,这个问题将得到解决……希望如此。感谢您的反馈!
  • 如果您希望顺序正确,您实际上是在要求串行执行,这意味着您从多线程中获得的唯一好处就是开销。现在,这并不是说每个任务本身可能不会在内部运行多线程,并且没有理由不这样做,但是从原子任务级别来看,您实际上希望它们是串行的,因此除非我缺少某些东西。
  • 对于#2,除非它已经存在,否则在DoWork() 函数中放置一个调试打印语句来显示参数,如果你还没有这个。我推测,有可能在两次调用中获得相同的参数。如果发生这种情况,请将 string Args 的声明移动到您的 if 块中。 (所以比现在晚了几行。)
  • 不是 100% 清楚你所说的序列化是什么意思。让我提供更多背景信息。正在启动的任务 (Oc2.exe) 读取一个 infile 并吐出文件。如果我按顺序跑步,1 次跑步可能需要 1 小时,另外 11 次跑步可能需要 1 小时(所以总共需要 2 小时)。但是,当我使用 .bat 脚本(就像我目前所做的那样)并首先启动较大的文件,然后运行其他 11 个(一次按 1 个)时,只需要略大于 1 小时。 Oc2.exe 运行完全独立于我的应用程序和彼此。我使用我的应用程序只是为了安排任务并按顺序运行(可能并发)。有意义吗??
【解决方案2】:

.Net 4.0 现在为简单的并行任务提供了一些新工具: http://msdn.microsoft.com/en-us/library/dd987838(v=vs.100).aspx

    string ExeName = "C:\\Program Files (x86)\\Norman\\bin\\OC2.exe";
    string Args;
    string ArgDir = "\\";

    //Simulated listbox items here:
    string[] filelist = new string[] {"file1", "file2"};

    System.Threading.Tasks.ParallelOptions options = new System.Threading.Tasks.ParallelOptions();
    options.MaxDegreeOfParallelism = 3;
    System.Threading.Tasks.Parallel.For(0, 10, options, (i => {

        Args = "-i " + filelist[i] + " -r -c -noerr";
        DoWork(ArgDir, ExeName, Args, "3");
    }
        ));
}

使用Parallel.For可以管理并发,任务按顺序启动。

在 .Net 2.0 中,您可以使用一些 BackgroundWorkers 和一个简单的控制器方法。比如:

Stack<string> filelist = new Stack<string>() {"file1", "file2"};

private void Setup()
{

    System.ComponentModel.BackgroundWorker backgroundWorker1;
    System.ComponentModel.BackgroundWorker backgroundWorker2;

    backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
    backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);

    backgroundWorker2.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
    backgroundWorker2.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
}

private void Execute()
{
    backgroundWorker1.DoWork();
    backgroundWorker2.DoWork();
}

private string GetNextItem()
{
    //this is a crude controller
    return filelist.Pop();
}

private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{   
    // Get the BackgroundWorker that raised this event.
    BackgroundWorker worker = sender as BackgroundWorker;
    string nextFile = GetNextItem();
    //Some simple check here to see if there were any items left or quit
    ....
    //Now do work
    Args = "-i " + nextFile  + " -r -c -noerr";
    DoWork(ArgDir, ExeName, Args, "3");

}

private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{   
    // Check for errors...
    ...

    // See if more work to do
    if (filelist.Count > 0)
    {
        //Repeat task
        BackgroundWorker worker = sender as BackgroundWorker;
        worker.DoWork();
    }
}

使用 BackgroundWorkers,您还可以拥有独立的进度条等。

代码未经测试。

【讨论】:

  • 我早些时候开始研究这个,但我的 VS 没有将 .Net 4.0 显示为目标,即使我知道我安装了它。等到另一个问题......谢谢你的例子!
  • 谢谢,我仍在努力让我的 VS2008 承认 .NET 4 框架。如果我不能尽快让它工作,我会研究你的后台工作人员方法。但是,乍一看,我似乎仅限于特定/硬编码的后台工作人员数量(在您的示例中为 2 个)。但我可能需要进一步研究,看看如何将其更改为动态数字。再次感谢您的示例。
  • 您可以创建后台工作人员的集合,在示例中键入的代码太多了。这也为您提供了一种操作和处理它们的简单方法。
猜你喜欢
  • 2018-09-09
  • 2020-09-02
  • 1970-01-01
  • 2010-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多