Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Gérer les échecs de build qui ne se produisent pas à chaque fois est une expérience frustrante. Cet article vous aidera à identifier la cause racine et à apporter des modifications qui vous permettront de corriger les échecs de build intermittents, afin que vos builds s’exécutent de manière cohérente chaque fois.
MSBuild prend en charge les builds parallèles en exécutant différents processus de nœud Worker sur différents cœurs de processeur. Bien que la génération en parallèle présente souvent des avantages significatifs en matière de performances, cela peut également entraîner le risque d’erreurs qui se produisent lorsque plusieurs processus tentent d’utiliser la même ressource en même temps. Cette situation est un type de condition de concurrence. Une condition de concurrence peut manifester un comportement différent d’une build à l’autre. Par exemple, un processus peut être en avance ou en retard sur l’autre par des durées différentes.
Les messages d’erreur qui proviennent d’une contention d’E/S de fichier incluent toujours un échec d’E/S de fichier du système d’exploitation, mais peuvent avoir des codes d’erreur MSBuild différents, en fonction de ce qui se passait dans la build lorsque l’erreur d’E/S de fichier s’est produite. Certains exemples peuvent ressembler à ce qui suit sur la plateforme Windows :
error MSB3677: Unable to move file "source" to "dest".
Cannot create a file when that file already exists. [{project file}]
The process cannot access the file 'file' because it is being used by another process.
Une concurrence de conflit de fichiers peut se produire lorsqu’un projet particulier est nécessaire pour générer avec plusieurs combinaisons de paramètres de propriété. MSBuild effectue généralement une build distincte pour un projet référencé chaque fois que les paramètres de propriété diffèrent, au cas où la sortie pourrait également différer. Selon le moment où les builds s’exécutent simultanément, les opérations de déplacement ou de copie peuvent échouer si un fichier est déjà présent au même emplacement, ou parce que le fichier de destination est utilisé par un autre processus MSBuild. En outre, les opérations de lecture de fichier peuvent échouer si un autre processus MSBuild lit ou écrit le même fichier.
Vous pouvez résoudre définitivement la plupart des problèmes de conflit de fichiers de build en comprenant la cause et en apportant les modifications appropriées dans les fichiers projet, mais uniquement si la cause se trouve dans votre propre code. Les conditions de concurrence peuvent également être provoquées par des bogues dans le code du SDK, auquel cas le problème doit être signalé et examiné par les propriétaires du SDK approprié.
Causes des conditions de concurrence de build
Cette section décrit différents types de problèmes qui peuvent se produire et conduire à des conditions de concurrence. La section suivante, Diagnostiquer et corriger les conditions de concurrence, décrit ce qu’il faut faire pour résoudre ces problèmes.
Paramètres de propriété ProjectReference incohérents
Différentes builds d’un même projet font normalement partie d’un grand nombre de processus de génération ; elles se produisent lorsque MSBuild génère une sortie pour plusieurs combinaisons de paramètres. Par exemple, une solution peut avoir plusieurs frameworks cibles (comme net472
et net7
) ou plusieurs architectures de plateforme cible (comme Arm64
et x64
). Cette exigence de build est satisfaite en spécifiant un dossier de sortie différent pour chaque combinaison de sorties. De cette façon, la version Arm64
net472
d’un assembly est générée dans un dossier différent des autres combinaisons et aucun conflit ne se produit. Les paramètres du SDK par défaut gèrent déjà les exemples mentionnés ici, mais parfois l’occurrence de plusieurs combinaisons de paramètres n’est pas aussi évidente et doit être examinée.
Les propriétés ProjectReference sont en conflit avec les propriétés globales
Les propriétés globales, c’est-à-dire lorsque vous définissez une propriété sur la ligne de commande avec l’option /p
ou /property
, sont implicitement utilisées pour les builds de projet référencées. Toutefois, en utilisant RemoveGlobalProperties
ou GlobalPropertiesToRemove
, vous pouvez omettre tout ou partie des paramètres de propriété globaux pour une référence de projet donnée. Par conséquent, si ces propriétés ne sont pas utilisées de manière cohérente, vous pouvez avoir une situation où plusieurs versions d’un projet référencé sont générées avec le jeu de propriétés globales, et une autre où elle est non définie ou a une valeur différente.
L’empaquetage déclenche involontairement des builds de projet
Si votre build empaquette la sortie des projets qui ont été générés précédemment, vous pouvez rencontrer une condition de concurrence lorsque votre logique de build d’empaquetage spécifie des paramètres de propriété différents de ceux des projets d’origine utilisés lors de leur génération. Dans ce cas, MSBuild déclenche normalement une régénération de ces projets en raison de l’incompatibilité des propriétés. Cette situation peut entraîner des conditions de concurrence. Envisagez de définir BuildProjectReferences
sur false
dans le projet d’empaquetage, afin que les projets en cours d’empaquetage ne soient jamais demandés à être générés. Cela signifie que la build d’empaquetage ne doit être demandée que lorsque les builds de projet sont précédemment effectuées et à jour.
Diagnostiquer et corriger les conditions de concurrence
Vous devez soupçonner une erreur de condition de concurrence lorsque les opérations de déplacement/copie ou les écritures de fichiers pour les fichiers générés par votre build échouent par intermittence.
L’approche pour résoudre le problème dépend du résultat souhaité. Avez-vous vraiment besoin de deux versions différentes du projet généré ? Si c’est le cas, faites en sorte que le dossier de sortie soit différent pour les deux configurations de propriété différentes. Si ce n’est pas le cas, vous pouvez modifier les éléments ProjectReference
pour vous assurer que les mêmes propriétés sont définies pour chaque référence.
Pour diagnostiquer et corriger la condition de concurrence, procédez comme suit.
Vérifiez que vous n’avez pas d’autre programme en cours d’exécution qui utilise ces fichiers, par exemple une session de débogueur Visual Studio sur le même ordinateur.
Déterminez si le problème disparaît si vous exécutez des builds avec l’option MSBuild
/m:1
. Consultez Informations de référence sur la ligne de commande MSBuild. Il s’agit de l’option de ligne de commande qui indique à MSBuild le nombre de nœuds à utiliser pour les builds. Si la valeur est 1, les builds se poursuivent en série et une condition de concurrence ne peut pas se produire. L’utilisation de l’option/m:1
est une solution de contournement que vous pouvez utiliser pour éviter la condition de concurrence, mais ce n’est pas une solution à long terme. Votre sortie de build est toujours générée plusieurs fois, avec des différences possibles, ce qui est une condition d’erreur appelée overbuild. En outre, la génération en série augmente considérablement le temps nécessaire à l’exécution d’une build. Si la build montre uniquement l’échec intermittent des E/S de fichier lorsque la génération parallèle est activée (le nombre de processeurs est supérieur à 1), il s’agit d’une condition de concurrence de build.Générez un journal. Exécutez une build avec la verbosité sur
Normal
ou plus (par exemple, utilisez l’option de ligne de commande-verbosity:Normal
). Pour résoudre les problèmes liés à la condition de concurrence, il est recommandé de générer un journal binaire (utilisez l’option/bl
ou/binlog
) et de l’afficher avec la Visionneuse de journal structuré. Pour obtenir un journal utile pour diagnostiquer une condition de concurrence, il n’est pas nécessaire que le journal provienne d’une exécution ayant échoué, car vous pouvez toujours localiser les emplacements multiples où la sortie qui a généré l’erreur est accessible.Que cette exécution ait échoué ou non, ouvrez le journal (ou fichier
.binlog
) et recherchez le nom de fichier du fichier qui déclenche l’échec et recherchez tous les emplacements où le fichier est utilisé.La capture d’écran suivante montre la visionneuse de journaux structurés affichant le journal généré par la génération de la solution dans l’exemple. Vous voyez les résultats de la recherche pour le fichier net5.0\Base.dll, qui a été mentionné dans un message d’erreur. Le même fichier de sortie apparaît deux fois comme
OutputAssembly
pour la tâcheCsc
dans les résultats de la recherche, ce qui indique qu’il est généré plusieurs fois.Notez les paramètres de propriété en vigueur pour chaque instance de la build de ce projet. La visionneuse de journaux structurés facilite cette tâche, car chaque build de projet a un nœud Propriétés répertoriant tous les paramètres de propriété en vigueur pour cette build de projet. Si vous utilisez un journal texte, les propriétés définies pour une build sont générées en texte lorsque le paramètre de verbosité est de
Normal
ou plus. Comparez les listes de propriétés pour chaque build du projet qui génère la sortie ayant échoué. Vous devriez voir une différence, si le problème est en fait une condition de concurrence.La capture d’écran suivante montre la visionneuse de journaux structurés, avec le nœud Propriétés développé pour une build de projet. Notez que cette build se trouve sous le nœud
ProjectReferences
d’un autre projet, ce qui signifie que cette build a été déclenchée par un élémentProjectReference
dans un autre projet. Si vous suivez les nœuds dans l’arborescence, vous pouvez voir quel projet les a référencés.Comparez la liste à l’autre build du même projet, et vous pouvez voir que
SpecialMode
est manquant. Cette build est une build de niveau supérieur, ce qui signifie qu’elle a été générée parce qu’elle était dans la solution elle-même, et non pas parce qu’elle a été référencée par un autre projet.Recherchez les fichiers projet pour
ProjectReference
où l’attributInclude
spécifie ce projet. Recherchez l’une des métadonnéesSetConfiguration
,SetPlatform
,SetTargetFramework
,AdditionalProperties
,RemoveGlobalProperties
ouGlobalPropertiesToRemove
. Vérifiez les différences dans les valeurs définies pour ces métadonnées entre différents élémentsProjectReference
dans la solution. Dans l’exemple, c’est le paramètre de métadonnées incohérentAdditionalProperties
(dans un emplacement, mais pas dans l’autre) qui est à l’origine du problème.Réfléchissez à la signification des paramètres de propriété qui sont différents et à la question de savoir s’ils ont un impact important sur la sortie de build. Si les différences de paramètre de propriété sont significatives et que la sortie est destinée à être distincte, le correctif consiste à utiliser un dossier différent pour chaque variante du paramètre, tout comme le fait le SDK .NET pour la plateforme, la configuration (par exemple, Débogage ou Production) ou l’infrastructure cible. Si la différence de paramètre de propriété est involontaire ou insignifiante, essayez de trouver un moyen de modifier le code du projet pour éliminer la différence dans les propriétés. Dans l’exemple, cela peut être effectué en ajoutant les métadonnées
AdditionalProperties="SpecialMode=true"
auProjectReference
dans Middle2.csproj, ou en supprimant les métadonnéesAdditionalProperties
de Middle1.csproj. La modification appropriée dépend des exigences spécifiques de votre application, service ou cible.Si l’erreur se trouve dans un SDK qui n’est pas sous votre contrôle, signalez le problème au propriétaire du SDK.
Exemple
Un cas simple illustrant le modèle est présenté ici. Supposons que vous ayez une solution avec plusieurs projets, un client frontal (App
), deux bibliothèques de classes (Middle1
et Middle2
) et une bibliothèque (Base
) qui est référencée par les deux bibliothèques de classes.
Les fichiers projet des sections de code suivantes font tous partie d’une solution unique. Cette collection de projets aboutit à deux builds différentes de Base
, l’une avec SpecialMode=true
et l’autre sans. Une erreur intermittente peut se produire en référençant la sortie Base.dll. Vous pouvez parfois obtenir une erreur indiquant que Base.dll
n’a pas pu être écrit, « car il est utilisé par un autre processus ».
<!-- Base.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Base\Base.csproj" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>
<!-- Middle1.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Base\Base.csproj" AdditionalProperties="SpecialMode=true" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>
<!-- Middle2.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Base\Base.csproj" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>
<!-- App.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Middle1\Middle1.csproj" />
<ProjectReference Include="..\Middle2\Middle2.csproj" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>
Si le comportement souhaité est d’utiliser SpecialMode
, le correctif approprié consiste à ajouter la même valeur de métadonnées AdditionalProperties
à ProjectReference
dans Middle2.csproj :
<!-- Middle2.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Base\Base.csproj" AdditionalProperties="SpecialMode=true" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>
Le problème de génération peut également être résolu en supprimant les métadonnées AdditionalProperties de Middle1.csproj, si cela correspond à l’intention.