我有一个ItemGroup,并希望并行处理所有项目(使用自定义任务或.exe)。
/maxCpuCount
参数一起使用,否则我可能会过度并行化。/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>
答案 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);
//...
}