并行处理批次项目

时间:2014-08-18 10:59:42

标签: msbuild

我有一个ItemGroup,并希望并行处理所有项目(使用自定义任务或.exe)。

  • 我可以编写我的task / exe来接受整个ItemGroup并在内部并行处理它的项目。但是,我希望这种并行性与MSBuild的/maxCpuCount参数一起使用,否则我可能会过度并行化。
  • This thread说没有办法。
  • 我的测试显示MSBuild的/maxCpuCount仅适用于构建不同的项目,而非项目(请参阅下面的代码)

如何并行处理ItemGroup中的项目?
有没有办法让自定义任务与MSBuild的Parallel支持并行工作?

<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="Build" >
    <!-- Runs only once - I guess MSBuild detects it's the same project -->
    <!--<MSBuild Projects="$(MSBuildProjectFullPath);$(MSBuildProjectFullPath)" Targets="Wait3000" BuildInParallel="true" />-->

    <!-- Runs in parallel!. Note that b.targets is a copy of the original a.targets -->
    <MSBuild Projects="$(MSBuildProjectFullPath);b.targets" Targets="Wait3000" BuildInParallel="true" />

    <!-- Runs sequentially -->
    <ItemGroup>
      <Waits Include="3000;2000"/>
    </ItemGroup>
    <Wait DurationMs="%(Waits.Identity)" />
  </Target>

  <Target Name="Wait3000">
    <Wait DurationMs="3000" />
  </Target>

  <UsingTask TaskName="Wait" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
    <ParameterGroup>
      <DurationMs ParameterType="System.Int32" Required="true" />
    </ParameterGroup>
    <Task>
      <Code Type="Fragment" Language="cs">
        Log.LogMessage(string.Format("{0:HH\\:mm\\:ss\\:fff}  Start  DurationMs={1}", DateTime.Now, DurationMs), MessageImportance.High);
        System.Threading.Thread.Sleep(DurationMs);
        Log.LogMessage(string.Format("{0:HH\\:mm\\:ss\\:fff}  End    DurationMs={1}", DateTime.Now, DurationMs), MessageImportance.High);
      </Code>
    </Task>
  </UsingTask>
</Project>   

2 个答案:

答案 0 :(得分:5)

我知道这已经过时了,但如果你花几分钟时间,请重新尝试使用MSBuild任务。使用Properties和/或AdditionalProperties保留项元数据元素*将解决您在代码示例中描述的问题(“仅运行一次 - 我想MSBuild会检测到它是同一个项目”)。

下面的MSBuild文件通过MSBuild的并行支持(包括/maxCpuCount)并行处理ItemGroup中的项目。它不使用MSBuild扩展包中的BuildTargetsInParallel,也不使用任何其他自定义或内联任务。

<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <Target Name="Build" >
    <ItemGroup>
      <Waits Include="3000;2000"/>
    </ItemGroup>

    <ItemGroup>
      <ProjectItems Include="$(MSBuildProjectFullPath)">
        <Properties>
          WaitMs=%(Waits.Identity)
        </Properties>
      </ProjectItems>
    </ItemGroup>
    <MSBuild Projects="@(ProjectItems)" Targets="WaitSpecifiedMs" BuildInParallel="true" />
  </Target>

  <Target Name="WaitSpecifiedMs">
    <Wait DurationMs="$(WaitMs)" />
  </Target>

</Project>

*隐藏在MSBuild Task reference page上的“属性元数据”下。

答案 1 :(得分:0)

正如你自己所说,you can't parallelize on target or task level, you can yield though

我的自定义任务使用TPL大量并行化,即我的基本任务包装器有一个ForEach包装器。

public bool ForEach<T>(IEnumerable<T> enumerable, Action<T> action, int max = -1)
{
    return enumerable != null && Parallel.ForEach(enumerable, new ParallelOptions { MaxDegreeOfParallelism = max }, (e, s) =>
    {
        if (Canceled)
            s.Stop();
        if (s.ShouldExitCurrentIteration)
            return;
        action(e);
        Interlocked.Increment(ref _total);
    }).IsCompleted;
}

通常,限制由.NET本身省略和管理,除了像MSDeploy这样的非线程安全操作,从单个IP部署配置DoS限制为20的SSRS报告,或者如果是的话,拉链任务会严重降级甚至超过CPU数量。可能不值得阅读maxCpuCount并使用Environment.ProcessorCount%NUMBER_OF_PROCESSORS%,但您可以尝试解析命令行或反映主机对象,例如我的基本任务类有这个方法来获取各种特殊全局标志的所有属性,目标等。

private void Engine(object host)
{
    var type = host.GetType();
    if (type.FullName != "Microsoft.Build.BackEnd.TaskHost")
    {
        Log.Warn("[Host] {0}", type.AssemblyQualifiedName);
        return;
    }

    var flags = BindingFlags.NonPublic | BindingFlags.Instance;
    var taskLoggingContext = type.GetProperty("LoggingContext", flags).GetValue(host, null);
    var targetLoggingContext = taskLoggingContext.GetType().GetProperty("TargetLoggingContext", flags).GetValue(taskLoggingContext, null);

    ProjectTask = taskLoggingContext.GetType().GetProperty("Task", flags).GetValue(taskLoggingContext, null).To<ProjectTaskInstance>();
    ProjectTarget = targetLoggingContext.GetType().GetProperty("Target", flags).GetValue(targetLoggingContext, null).To<ProjectTargetInstance>();

    var entry = type.GetField("requestEntry", flags).GetValue(host);
    var config = entry.GetType().GetProperty("RequestConfiguration").GetValue(entry, null);

    Project = config.GetType().GetProperty("Project").GetValue(config, null).To<ProjectInstance>();
    Properties = Project.Properties.ToDictionary(p => p.Name, p => p.EvaluatedValue);

典型任务使用ForEach看起来像这样:

public class Transform : Task
{
    [Required]
    public ITaskItem[] Configs { get; set; }

    protected override void Exec()
    {
        //...

        ForEach(Configs, i =>
        {
            //...
        }, Environment.ProcessorCount);

        //...
    }
相关问题