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
<PackageReference Include="Net4x.AppDomainTaskLibrary" Version="1.10.0.2" />
<PackageVersion Include="Net4x.AppDomainTaskLibrary" Version="1.10.0.2" />
<PackageReference Include="Net4x.AppDomainTaskLibrary" />
paket add Net4x.AppDomainTaskLibrary --version 1.10.0.2
#r "nuget: Net4x.AppDomainTaskLibrary, 1.10.0.2"
#:package Net4x.AppDomainTaskLibrary@1.10.0.2
#addin nuget:?package=Net4x.AppDomainTaskLibrary&version=1.10.0.2
#tool nuget:?package=Net4x.AppDomainTaskLibrary&version=1.10.0.2
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 | Versions 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. |
-
.NETFramework 3.5
- No dependencies.
-
.NETFramework 4.0
- No dependencies.
-
.NETFramework 4.5
- No dependencies.
-
.NETFramework 4.6.1
- No dependencies.
-
.NETStandard 2.0
- Net4x.AppDomain (>= 2.0.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 |