17
votes

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.

2
That's not the same question. I'm having the same problem, and the problem is NOT that the task assembly is locked (which is understandable). The problem is that any dll file that you load within your task (e.g. Assembly.LoadFrom()) will never get released even after ur task's AppDomain is disposed, and even if you create another AppDomain by yourself and load the dll from there: the dll is still locked after you shut the AppDomain. The same code works OK on a console app (the program is still running, but the dll will be released if u shut the appdomain), but on VS task it behaves differentlySheepy
This is not an answer to your question but if you are doing some kind of codegen from within VS IDE, while the IDE is running, then you are probably better off leveraging either T4 templates or a VS Custom Tool. This should neatly side step your issue, especially if you use T4 Templates.Umar Farooq Khawaja
Did you ever find a solution to this problem? I am seeing something similar.Darrell

2 Answers

3
votes

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.

Useful feature

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

  1. You can convert your task to an inline code task.
  2. You can use an inline code task to load your static task and delegate to it.

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.

Class Library1 is not loaded

-4
votes

It can be resolved by changing the file system permission security. put your current user like co-owner of the locked file.