At first glance, my problem seemed to be a common one: I have a custom MS Build task in some of my projects. Once I compile the projects, I cannot compile the build task any more - the build task assembly is locked by Visual Studio.
I found a lot of posts here saying 'Just inherit from AppDomainIsolatedTask'.
My task already does. The assembly contains nothing else then this task. The AppDomain seems to be unloaded, at least the DomainUnload event is fired. And, dependent assemblies are unloaded correctly.
However, the assembly containing the build task itself is locked by devenv.exe (which I double checked by ProcessExplorer).
I found another post saying 'Set the GenerateResourceNeverLockTypeAssemblies property true', which sounded promising but didn't help either.
So, I wonder what else may go wrong. The behavior is the same no matter if I use VS2008 or 2010.
This kind of thing can be very tricky to get right. There is a lot to consider LoaderOptimization, ShadowCopy etc.
AppDomainIsolatedTask
does not mean "do not pollute the build domain".
It means "run my code in another domain" - the task type itself is still loaded into the build engine domain. This is unfortunate as I feel most people interpret it as the former. For this specialized task type to work you must also decoarate your task with [LoadInSeparateAppDomain]
. This still will not solve your problem.
ProcessExplorer has this handy .NET assemblies view which you can use to identify which domain has a handle to your assembly. As you already know devenv.exe has it locked.
Since you load the DLL that has the task into the domain that VS uses for building stuff you are going to run into this problem.
What you need to do is introduce another layer of indirection.
Options
I have provided an example for approach #2 This screenshot validates the results that my custom library is not present in the MSBuild appdomain.
Define a inline task like so
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask
TaskName="RunOtherTask"
TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll" >
<ParameterGroup />
<Task>
<Using Namespace="MyTasks" />
<Code Type="Class" Language="cs">
<![CDATA[
using System;
using System.Reflection;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace MyTasks {
public class RunOtherTask : Task {
public override bool Execute() {
var remoteTask = CreateInstanceInSeparateDomain();
remoteTask.BuildEngine = this.BuildEngine;
remoteTask.Execute();
return true;
}
private ITask CreateInstanceInSeparateDomain() {
var thisAssembly = Assembly.GetExecutingAssembly();
var setup = new AppDomainSetup {
ShadowCopyFiles = true.ToString(),
LoaderOptimization = LoaderOptimization.MultiDomainHost
};
var domain = AppDomain.CreateDomain("MyDomain", null, setup);
var handle = domain.CreateInstanceFrom(@"C:\ClassLibrary1.dll", "ClassLibrary1.SimpleTask");
return (ITask)handle.Unwrap();
}
}
}
]]>
</Code>
</Task>
</UsingTask>
<Target Name="RunTask">
<RunOtherTask />
</Target>
</Project>
As long as your task implements ITask (which it must) it will work. You need to set the engine property on the newly instantiated task for everything to work correctly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With