Parallel, Graph and Server Builds
|
This section documents MSBuild 18.9, the .NET SDK 10.0.402 (with its This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The C# Reference bibliography lists the reference material consulted while preparing these pages. |
A single project builds its targets one after another, but a repository of many projects can build several projects
at once, and successive builds can reuse warm processes. MSBuild offers three mechanisms: worker nodes (parallel
processes, -m), the static graph (-graph, an up-front dependency graph instead of discovering references while
building), and MSBuild Server (a long-lived process that keeps evaluation caches between dotnet commands). This
page measures them on Ledgerline with MSBuild 18.9 (.NET SDK 10.0.402) on a 4-core Linux machine.
Worker nodes and -maxCpuCount
With -m[:n] MSBuild starts up to n worker processes ("nodes"); each project is assigned to a free node, and within
a node a project’s targets run serially. dotnet build passes -maxcpucount (one node per core) by default;
msbuild.exe builds on one node unless told otherwise. A rebuild of the Ledgerline solution (six projects, one of them
with three TFMs):
dotnet build Ledgerline.slnx --no-restore --no-incremental -m:1 -tl:off -v:q
dotnet build Ledgerline.slnx --no-restore --no-incremental -m:4 -tl:off -v:q
Time Elapsed 00:00:03.27
Time Elapsed 00:00:02.06
-ds (-detailedSummary) prints, at normal verbosity, which configuration ran where and for how long — the first tool to
read when a parallel build is slower than expected:
============================== Build Hierarchy (IDs represent configurations) =====================================================
Id : Exclusive Time Total Time Path (Targets)
-----------------------------------------------------------------------------------------------------------------------------------
0 : 0.057s 1.777s ~/ledgerline/Ledgerline.slnx (Rebuild)
| 1 : 1.026s 1.026s ~/ledgerline/samples/Ledgerline.Report/Ledgerline.Report.csproj (Rebuild)
| 4 : 0.344s 0.344s ~/ledgerline/src/Ledgerline.Templates/Ledgerline.Templates.csproj (Rebuild)
| 3 : 0.550s 1.348s ~/ledgerline/src/Ledgerline.Cli/Ledgerline.Cli.csproj (Rebuild)
…
============================== Node Utilization (IDs represent configurations) ====================================================
Timestamp: 1 2 4 3 Duration Cumulative
-----------------------------------------------------------------------------------------------------------------------------------
639283777188399085: 0 x x x 0.041s 0.041s
639283777188807904: 1 x x x 0.050s 0.091s .
639283777189308164: | 2 3 4 0.240s 0.330s ....
…
The MSBuild task builds its Projects on several nodes when BuildInParallel is true, its default; within one
project, everything else is sequential.
Source: MSBuild: -maxcpucount switch, BuildInParallel task parameter, Use multiple processors to build projects, Parallel execution.
Node reuse
Worker nodes stay alive after the build (-nodeReuse:true, the default) so the next build skips their start-up. After a
dotnet build of the solution the processes are still there:
ps -eo args | grep nodemode
3 MSBuild.dll ... /nodemode:1 /nodeReuse:true
1 <dotnet>/sdk/10.0.402/MSBuild /nologo /nodemode:2 /nodereuse:True /low:False /parentpacketversion:4
(counted with uniq -c; /nodemode:1 are worker nodes). On a CI agent that must not leave processes behind, or after
changing a custom task’s DLL, use -nodeReuse:false (-nr:false) or shut them down with dotnet build-server shutdown.
Static graph builds
By default, MSBuild discovers project references while building: when a project reaches ResolveProjectReferences, it
calls the MSBuild task on its references, pausing until they are built. With -graph (-graphBuild), MSBuild first
evaluates every project, builds the dependency graph from ProjectReference items, and then schedules the graph
bottom-up.
dotnet build Ledgerline.slnx -graph --no-restore -tl:off -v:m
Static graph construction started.
Static graph loaded in 0.566 seconds: 9 nodes, 11 edges
Ledgerline -> ~/ledgerline/src/Ledgerline/bin/Debug/netstandard2.0/Ledgerline.dll
Ledgerline.Report -> ~/ledgerline/samples/Ledgerline.Report/bin/Debug/net10.0/Ledgerline.Report.dll
…
Ledgerline.Tests -> ~/ledgerline/tests/Ledgerline.Tests/bin/Debug/net10.0/Ledgerline.Tests.dll
Build succeeded.
The graph has nine nodes for six projects because the multi-targeted library contributes an outer node and three inner
ones. A graph build is only correct when the graph is complete: a project that builds another project which it does
not declare as a ProjectReference would build it outside the graph.
Source: MSBuild: Graph option, Project references.
Isolated builds
-isolate (-isolateProjects) turns that rule into an error: a project may only build the targets of the references it
declares, with the targets it declares for them through ProjectReferenceTargets items. Three plain projects — Report.proj references Ledger.proj and also calls Audit.proj directly:
<Project>
<ItemGroup>
<ProjectReference Include="Ledger.proj" />
<!-- Tells the graph which targets this project calls on its references. -->
<ProjectReferenceTargets Include="Build" Targets="Build" />
</ItemGroup>
<Target Name="Build">
<MSBuild Projects="@(ProjectReference)" Targets="Build" />
<!-- Not declared as a reference: forbidden under -isolate. -->
<MSBuild Projects="Audit.proj" Targets="Build" />
<Message Importance="high" Text="Report built" />
</Target>
</Project>
dotnet msbuild Report.proj -nologo -v:m
dotnet msbuild Report.proj -graph -isolate -nologo -v:m
Ledger built
Audit built
Report built
Static graph construction started.
Static graph loaded in 0.103 seconds: 2 nodes, 1 edges
Ledger built
~/graph/Report.proj(10,5): error : MSB4254: The MSBuild task is building project(s) ""~/graph/Audit.proj"" which are not specified in the ProjectReference item. In isolated builds this probably means that the references are not explicitly specified as a ProjectReference item in "~/graph/Report.proj" ~/graph/Report.proj
Without the ProjectReferenceTargets item, even the declared reference fails, with MSB4252 ("…the build result for
the built project is not in the engine cache… the reference was called with a target which is not specified in the
ProjectReferenceTargets item"). The SDK declares ProjectReferenceTargets for its own targets, which is why SDK projects
build under -graph -isolate without changes; custom targets that call references need their own items.
Isolation is the prerequisite for result caches: -orc:<file> (-outputResultsCache) writes a project’s results,
and -irc:<files> (-inputResultsCaches) lets a later build use them instead of rebuilding the references — a building
block for distributed build systems, documented only as switches.
MSBuild Server
MSBuild Server is a long-lived process (/nodemode:8) that hosts the build itself, so successive dotnet build
commands skip start-up and reuse evaluation caches. It is off by default on .NET 10 and enabled with
DOTNET_CLI_USE_MSBUILD_SERVER=true (or 1); it is on by default from the .NET 11 SDK, which is a preview
(DOTNET_CLI_USE_MSBUILD_SERVER=false opts out; the .NET 11 CLI also stops overwriting MSBUILDUSESERVER with 0,
so that MSBuild variable can be used directly). -mt builds always use it. It helps
developer inner loops, not CI agents that build once.
DOTNET_CLI_USE_MSBUILD_SERVER=1 dotnet build Ledgerline.slnx --no-restore -tl:off -v:q
ps -eo args | grep "nodemode:8"
dotnet build-server shutdown
1 <dotnet>/sdk/10.0.402/MSBuild /nologo /nodemode:8
Shutting down MSBuild server...
Shutting down VB/C# compiler server...
VB/C# compiler server shut down successfully.
MSBuild server shut down successfully.
dotnet build-server shutdown stops MSBuild Server, the worker nodes and the Roslyn compiler server (--msbuild,
--vbcscompiler and --razor select one). -nodeReuse:false disables the server for one build, because the server is a
form of node reuse; -help, -version and binary-log replays never use it. A -v:diag log or a binary log records
whether the server was started, reused or bypassed.
Common mistakes
-
msbuild.exewithout-mon CI — one node;dotnet buildalready parallelises. -
Shared output paths between projects — parallel builds race on the same files (
MSB3026/MSB3027on Windows). -
Custom targets that build other projects with the
MSBuildtask without declaring them — wrong under-graph, errors under-isolate. -
Stale task DLLs held by reused nodes —
dotnet build-server shutdownor-nr:false. -
Expecting MSBuild Server on .NET 10 by default — it is opt-in until .NET 11.