Net4x.AppDomainTaskLibrary 1.10.0.2

dotnet add package Net4x.AppDomainTaskLibrary --version 1.10.0.2
                    
NuGet\Install-Package Net4x.AppDomainTaskLibrary -Version 1.10.0.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Net4x.AppDomainTaskLibrary" Version="1.10.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Net4x.AppDomainTaskLibrary" Version="1.10.0.2" />
                    
Directory.Packages.props
<PackageReference Include="Net4x.AppDomainTaskLibrary" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Net4x.AppDomainTaskLibrary --version 1.10.0.2
                    
#r "nuget: Net4x.AppDomainTaskLibrary, 1.10.0.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Net4x.AppDomainTaskLibrary@1.10.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Net4x.AppDomainTaskLibrary&version=1.10.0.2
                    
Install as a Cake Addin
#tool nuget:?package=Net4x.AppDomainTaskLibrary&version=1.10.0.2
                    
Install as a Cake Tool

Net4x.AppDomainTaskLibrary

Run work in an isolated application domain and await the result as a Task.

Two things: a manager that loads an assembly into its own domain and reflects over it, and a marshaller that runs a worker there and hands back a Task<string>.

net35  net40  net45  net461     classic in-process AppDomain
netstandard2.0                  each domain is a separate OS process

Getting started

using AppDomainTaskLibrary.AppDomains;

[Serializable]                              // required: the worker is copied into the domain
public class Greeter : BaseWorker
{
    public override string DoWork() => "hello from " + Process.GetCurrentProcess().Id;
}

using (var manager = AssemblyReflectionManager.CreateInstance())
{
    manager.LoadAssembly(typeof(Greeter).Assembly.Location, "greeting-domain", out var domain);

    var result = await CrossDomainTaskMarshaler.CreateInstance()
        .DoWorkInOtherDomain(domain, new Greeter(), typeof(Greeter).Assembly.Location);

    manager.UnloadDomain("greeting-domain");
}

Dispose on the manager unloads every domain it created, so the using above is the safety net when UnloadDomain is never called explicitly.

On .NET Framework (net35 – net461)

A domain is a System.AppDomain in this process, as it always was. Nothing here has changed.

net35 has no Task, so CrossDomainTaskMarshaler is absent from that target; AssemblyReflectionManager is present on all of them.

On netstandard2.0

System.AppDomain cannot create or unload domains off .NET Framework — AppDomain.CreateDomain still compiles there and then throws PlatformNotSupportedException. This target is built on Net4x.AppDomain, which restores the capability by making each domain a separate operating-system process, reached over a System.Runtime.Remoting compatibility layer.

That buys stronger isolation than .NET Framework ever offered — a domain that stack-overflows, corrupts native memory, or calls Environment.FailFast cannot take the parent down with it — and it changes five things worth knowing before you use it.

1. The executable must also reference the domain host

<PackageReference Include="Net4x.AppDomain.Host" Version="2.0.0" />

In the application, not in a library. The host is the program a domain actually runs in, and the targets that copy it next to your output ship in that package's build/ folder, which does not flow through a library that merely references it. Without it the first LoadAssembly fails, listing every path it searched.

The host is a .NET 8 program, so although the target framework is netstandard2.0 this target is in practice usable from .NET 8 or later. A .NET Framework 4.7.2 application will resolve lib/netstandard2.0 happily and then fail at the first domain creation — use one of the net4x targets there instead.

2. The domain handle is ChildAppDomain, not AppDomain

Net4x.AppDomain cannot redefine System.AppDomain, because the platform owns that name. So the signatures differ by target framework:

// net35 – net461
bool LoadAssembly(string assemblyPath, string domainName, out AppDomain appDomain);
// netstandard2.0
bool LoadAssembly(string assemblyPath, string domainName, out ChildAppDomain appDomain);

var absorbs the difference at most call sites. ChildAppDomain adds ProcessId and IsAlive, neither of which means anything in-process.

3. Marshal<T> is not available

Marshal<T>(domain, Func<Task<T>>, string[]) exists only on the .NET Framework targets. It is there to send an arbitrary delegate into the domain, and a closure cannot cross a process boundary: its target is not a MarshalByRefObject, so it cannot be sent — and making it one would be worse, because an instance delegate is marshalled by reference, so the work would run back in the calling process and silently defeat the isolation you asked for.

Use DoWorkInOtherDomain. Its payload is the [Serializable] worker itself, which genuinely copies, so put the state the work needs in fields:

[Serializable]
public class Sum : BaseWorker
{
    private readonly int[] _values;
    public Sum(int[] values) => _values = values;
    public override string DoWork() => _values.Sum().ToString();
}

4. Reflect needs a static method, passed as a method group

The callback runs inside the domain's process, so only its return value crosses the boundary — which is what keeps the isolation real. That needs a delegate the far side can rebuild, and only a static method qualifies:

static class Reflectors
{
    public static int CountTypes(Assembly assembly) => assembly.GetExportedTypes().Length;
}

manager.Reflect(path, Reflectors.CountTypes);                    // works
manager.Reflect(path, a => a.GetExportedTypes().Length);          // throws
manager.Reflect(path, static a => a.GetExportedTypes().Length);   // throws as well

A lambda does not qualify even when it captures nothing, and not even with the C# static modifier: the compiler binds it to a cached closure instance, and static only forbids captures. Such a delegate is rejected with a NotSupportedException naming the fix, rather than quietly reflecting over nothing.

The return value has to be serializable, and the assembly declaring the static method has to be loadable in the domain — pass it in assembliesToLoad.

5. AppDomain.CurrentDomain.FriendlyName inside a worker is not the domain's name

The worker runs in the host process and sees that process's own default domain. Read ChildAppDomain.FriendlyName in the parent when the domain's identity is what you want.

Cost

A domain is a process: creating one costs tens of milliseconds rather than one. Reuse domains rather than creating one per unit of work.


Copyright (c) Piero Viano. All rights reserved. Piero Viano

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net35 is compatible.  net40 is compatible.  net403 was computed.  net45 is compatible.  net451 was computed.  net452 was computed.  net46 was computed.  net461 is compatible.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETFramework 3.5

    • No dependencies.
  • .NETFramework 4.0

    • No dependencies.
  • .NETFramework 4.5

    • No dependencies.
  • .NETFramework 4.6.1

    • No dependencies.
  • .NETStandard 2.0

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Net4x.AppDomainTaskLibrary:

Package Downloads
Net4x.InputBoxLibrary.BaseInputBoxTests

BaseInputBox Tests

Net4x.OcrLibrary

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.10.0.2 0 7/30/2026
1.9.0.1 190 12/30/2025
1.9.0 280 4/4/2025
1.8.1 282 8/26/2023
1.8.0-at20230502043308 266 5/7/2023