WSL 容器 C# API 实战:深入解析 Microsoft.WSL.Containers 的 Session 会话管理类
【免费下载链接】WSLWindows Subsystem for Linux项目地址: https://gitcode.com/GitHub_Trending/ws/WSL
本指南以
Microsoft.WSL.ContainersC# API 的核心类Session为主线,系统讲解如何创建、启动、终止一个 WSL 容器主机会话,以及如何在会话内完成容器创建、镜像拉取/导入/加载/推送/删除/打标签、VHD 卷管理与注册表认证等完整操作。读完本文,你将掌握 WSL 容器会话生命周期管理、带进度回调的异步镜像操作,以及底层 WinRT 封装与原生 SDK 的调用关系,可直接据此编写可运行的容器编排程序。
Session 类总览
Session是 WSL 容器编程模型中的核心入口,它表示一个由 WSL 支撑的容器主机会话(WSL-backed container host session)。所有容器和镜像操作都以会话为宿主展开:镜像必须先被拉取到会话中,容器必须由会话创建,会话本身则对应着一个独立运行的 WSL 虚拟机。
public sealed class Session : IDisposable { public Session(SessionSettings settings); public event SessionTerminationHandler Terminated; public event ProcessCrashHandler ProcessCrashed; public void Start(); public void Terminate(); public Container CreateContainer(ContainerSettings containerSettings); public void PullImage(PullImageOptions options); public IAsyncActionWithProgress<ImageProgress> PullImageAsync(PullImageOptions options); public void ImportImage(string path, string imageName); public IAsyncActionWithProgress<ImageProgress> ImportImageAsync(string path, string imageName); public void LoadImage(string path); public IAsyncActionWithProgress<ImageProgress> LoadImageAsync(string path); public void PushImage(PushImageOptions options); public IAsyncActionWithProgress<ImageProgress> PushImageAsync(PushImageOptions options); public void DeleteImage(string nameOrId); public void TagImage(TagImageOptions options); public void CreateVhdVolume(VhdOptions options); public void DeleteVhdVolume(string name); public string Authenticate(Uri serverAddress, string username, string password); public IReadOnlyList<ImageInfo> GetImages(); public void Dispose(); }从类型特征上看,Session是sealed的(不可继承)、实现了IDisposable(需要显式释放底层资源)。从官方 API 概览可知,这套公开的 C# 表面镜像自 WinRT 表面,其真实实现位于src/windows/WslcSDK/winrt/下的Session.h/Session.cpp封装层。当前仓库中 C# 投影的占位实现位于 src/windows/WslcSDK/csharp/Projection.cs,实际能力由 WinRT 层与原生Wslc*SDK 函数提供。
创建会话:构造函数与 SessionSettings
会话只能通过构造参数创建,没有无参构造函数:
var session = new Session(sessionSettings);SessionSettings负责在Start()之前完成会话的全部配置,其定义见 settings-classes/sessionsettings.md:
public sealed class SessionSettings { public SessionSettings(string name, string storagePath); public string Name { get; set; } public string StoragePath { get; set; } public uint? CpuCount { get; set; } public uint? MemorySizeInMB { get; set; } public TimeSpan? Timeout { get; set; } public VhdOptions VhdRequirements { get; set; } public bool EnableGpu { get; set; } }各字段的语义与注意事项:
Name:会话的显示名称,同时也是机器级的标识键。会话名对机器上所有用户可见(还包括创建者 SID 和创建进程 PID),因此切勿把凭据或敏感信息放进会话名。若同名会话已存在,创建会失败并返回ERROR_ALREADY_EXISTS。StoragePath:会话存储写入路径;如果路径不存在会被自动创建。CpuCount、MemorySizeInMB、Timeout:均可选的 nullable 值。其中Timeout必须为正数,且数值必须能放进 uint32 毫秒计数(即不能超过约 49.7 天)。VhdRequirements:会话级存储需求描述,可选;这里不允许设置Owner(Owner专用于命名卷创建,见后文 VHD 卷小节)。EnableGpu:是否启用 GPU 加速。
一个完整的配置示例:
var sessionSettings = new SessionSettings("demo-session", @"C:\WslcData") { CpuCount = 4, MemorySizeInMB = 4096, Timeout = TimeSpan.FromMinutes(5), EnableGpu = true };启动与终止:Start() / Terminate()
Session.Start()
启动会话 VM,并注册内部终止等待(termination wait):
session.Start();从 WinRT 实现 src/windows/WslcSDK/winrt/Session.cpp 可以看到Start()内部其实完成了三件事:
- 调用原生
WslcCreateSession创建底层会话句柄,失败时抛出带错误消息的异常;成功后会释放m_settings(设置仅在Start()之前保留)。 - 调用
WslcGetSessionTerminationEvent取得会话终止事件句柄,并通过CreateThreadpoolWait+SetThreadpoolWait注册一个线程池等待,一旦终止事件被触发就回调Session::OnTerminated——这正是Terminated事件的底层来源。 - 调用
WslcRegisterSessionCrashDumpCallback注册崩溃转储回调(对应ProcessCrashed事件)。
Session.Terminate()
终止会话:
session.Terminate();底层直接调用WslcTerminateSession完成(见 Session.cpp)。
需要特别说明的是,Start()与Terminate()之外的绝大多数方法都受EnsureStarted()保护:如果会话尚未启动,会抛出hresult_illegal_method_call("Session has not been started");重复调用Start()则会抛出 "Session has already been started"。因此任何镜像、容器、卷操作都必须放在Start()之后。
会话事件:Terminated 与 ProcessCrashed
会话暴露两个事件,均以标准 C# 事件形式消费(WinRT 委托投影为普通 C# 委托,详见 delegates-and-events.md)。
Terminated 事件
当会话终止事件被触发时引发:
session.Terminated += reason => Console.WriteLine($"Session terminated: {reason}");委托签名:public delegate void SessionTerminationHandler(SessionTerminationReason reason);,其中SessionTerminationReason枚举定义了会话终止的原因分类(见 enumerations/sessionterminationreason.md)。事件底层由线程池等待Session::OnTerminated回调驱动。
ProcessCrashed 事件
当上报进程崩溃转储时引发:
session.ProcessCrashed += information => Console.WriteLine($"Process crashed: {information.ProcessName} ({information.Pid})");委托签名:public delegate void ProcessCrashHandler(ProcessCrashInformation information);,ProcessCrashInformation数据类携带进程名、PID 等崩溃信息(见>Container container = session.CreateContainer(containerSettings);
ContainerSettings至少需要指定镜像(如new ContainerSettings("alpine:latest")),还可配置容器名称、初始化进程(InitProcess)、网络模式、端口映射、卷挂载等,详见 core-classes/container.md 与 settings-classes/containersettings.md。创建出的Container可进一步执行Start()、Stop(Signal, TimeSpan)、Delete(...)等生命周期操作,其核心使用方式可参考 end-to-end-example.md。
镜像管理:拉取、导入、加载、推送、删除与打标签
Session是镜像操作的统一入口,所有方法都成对提供「同步版」与「带进度回调的异步版」。
拉取镜像:PullImage / PullImageAsync
同步拉取:
session.PullImage(new PullImageOptions("docker.io/library/alpine:latest"));带进度的异步拉取:
var pull = session.PullImageAsync(new PullImageOptions("docker.io/library/alpine:latest")); pull.Progress = (op, progress) => Console.WriteLine($"pull: {progress.Status} {progress.Id} {progress.CurrentBytes}/{progress.TotalBytes}"); await pull;PullImageOptions的定义(见 settings-classes/pullimageoptions.md):
public sealed class PullImageOptions { public PullImageOptions(string uri); public string Uri { get; set; } public string RegistryAuth { get; set; } }Uri为完整镜像引用(如docker.io/library/alpine:latest);RegistryAuth为注册表认证令牌,公共注册表可留空:
var pullOptions = new PullImageOptions("docker.io/library/alpine:latest") { RegistryAuth = string.Empty // optional for public registries };导入镜像:ImportImage / ImportImageAsync
从文件路径同步导入镜像 tarball:
session.ImportImage(@"C:\images\demo.tar", "demo:imported");异步导入:
var importOp = session.ImportImageAsync(@"C:\images\demo.tar", "demo:imported"); importOp.Progress = (op, progress) => Console.WriteLine($"import: {progress.Status} {progress.Id}"); await importOp;第二个参数imageName指定导入后的镜像名称(含标签,如demo:imported)。
加载镜像:LoadImage / LoadImageAsync
从磁盘加载镜像归档(适用于docker save产生的 tar 文件):
session.LoadImage(@"C:\images\docker-save.tar");异步加载:
var loadOp = session.LoadImageAsync(@"C:\images\docker-save.tar"); loadOp.Progress = (op, progress) => Console.WriteLine($"load: {progress.Status} {progress.Id}"); await loadOp;推送镜像:PushImage / PushImageAsync
同步推送到注册表:
session.PushImage(new PushImageOptions("registry.example.com/demo:latest", authToken));异步推送:
var pushOp = session.PushImageAsync(new PushImageOptions("registry.example.com/demo:latest", authToken)); pushOp.Progress = (op, progress) => Console.WriteLine($"push: {progress.Status} {progress.Id}"); await pushOp;PushImageOptions(见 settings-classes/pushimageoptions.md):
public sealed class PushImageOptions { public PushImageOptions(string image, string registryAuth); public string Image { get; set; } public string RegistryAuth { get; set; } }删除镜像:DeleteImage()
按名称或 ID 删除镜像:
session.DeleteImage("demo:old");镜像打标签:TagImage()
为已有镜像附加新的仓库/标签:
session.TagImage(new TagImageOptions("alpine:latest", "registry.example.com/alpine", "v1"));TagImageOptions(见 settings-classes/tagimageoptions.md)由三个必填参数构成:Image(源镜像)、Repository(目标仓库)、Tag(目标标签)。
镜像操作进度与结果数据
所有异步镜像操作统一通过IAsyncActionWithProgress<ImageProgress>上报进度。ImageProgress(见>public sealed class ImageProgress { public string Id { get; } public ImageProgressStatus Status { get; } public ulong CurrentBytes { get; } public ulong TotalBytes { get; } }
其中Status为ImageProgressStatus枚举(见 enumerations/imageprogressstatus.md),CurrentBytes/TotalBytes用于计算传输进度:
void PrintImageProgress(ImageProgress progress) => Console.WriteLine($"{progress.Status,-12} {progress.Id} {progress.CurrentBytes}/{progress.TotalBytes}");查询镜像列表:GetImages()
返回会话已知镜像的快照:
foreach (var image in session.GetImages()) { Console.WriteLine(image.Name); }返回项为ImageInfo(见>using Windows.Storage.Streams; public sealed class ImageInfo { public string Name { get; } public IBuffer Sha256 { get; } public ulong Size { get; } public DateTimeOffset CreatedTimestamp { get; } }
一个更实用的格式化输出:
foreach (var image in session.GetImages()) { Console.WriteLine($"{image.Name} ({image.Size / 1024 / 1024} MB)"); }注册表认证:Authenticate()
向注册表服务器认证并返回身份令牌字符串:
string token = session.Authenticate( new Uri("https://registry.example.com"), "user1", "password");Authenticate接收服务器地址(Uri)、用户名与密码。底层对应WslcSessionAuthenticate调用(见 Session.cpp);需要说明的是,在 WinRT 原生层该接口返回的是AuthenticateResult类型(见 src/windows/WslcSDK/winrt/Session.h),而 C# 投影中简化为直接返回字符串形式的身份令牌。获取到的令牌可继续用于PullImageOptions.RegistryAuth、PushImageOptions.RegistryAuth等私有注册表操作。
VHD 卷管理:CreateVhdVolume / DeleteVhdVolume
会话还支持管理命名的会话 VHD 卷。创建:
var vhd = new VhdOptions("cache", 2UL * 1024 * 1024 * 1024, VhdType.Dynamic) { Owner = new VhdOwner { Uid = 1000, Gid = 1000 } }; session.CreateVhdVolume(vhd);删除:
session.DeleteVhdVolume("cache");VhdOptions(见 settings-classes/vhdoptions.md)同时承担两种职责:
public sealed class VhdOptions { public VhdOptions(string name, ulong size, VhdType type); public string Name { get; set; } public ulong Size { get; set; } public VhdType Type { get; set; } public VhdOwner? Owner { get; set; } }使用要点:
- 会话级存储需求:通过
SessionSettings.VhdRequirements指定(此时不允许设置Owner); - 命名会话卷:通过
Session.CreateVhdVolume(...)创建(此时可设置Owner,其中VhdOwner携带Uid/Gid以表达卷内文件属主); VhdType枚举(见 enumerations/vhdtype.md)决定卷类型,示例中使用的是VhdType.Dynamic(动态扩展)。
释放资源:Dispose()
Session实现IDisposable,Dispose()用于释放底层 WinRT 会话对象:
session.Dispose();从 WinRT 封装看,会话句柄由wil::unique_any<WslcSession, WslcReleaseSession>持有(见 src/windows/WslcSDK/winrt/Session.h),句柄释放时会经由WslcReleaseSession归还给原生 SDK。最佳实践是结合using语句或try/finally保证会话资源被及时回收,并在程序退出前调用session.Terminate()干净地关闭会话 VM。
端到端示例:完整容器生命周期
下面的完整程序(来源:end-to-end-example.md)串联了本文全部核心概念——检查前置条件、创建会话、拉取镜像、创建并启动容器、等待初始化进程退出、清理并终止会话:
using Microsoft.WSL.Containers; using System; using System.Text; using System.Threading.Tasks; class Program { static async Task<int> Main() { // 0. Check prerequisites var missing = WslcService.GetMissingComponents(); if (missing.Count > 0) { Console.WriteLine("WSL components are missing. Run: wsl --install"); return 1; } var ver = WslcService.GetVersion(); Console.WriteLine($"WSL version: {ver.Major}.{ver.Minor}.{ver.Revision}"); // 1. Create a session var sessionSettings = new SessionSettings("MyApp", @"C:\WslcData") { CpuCount = 4, MemorySizeInMB = 4096 }; var session = new Session(sessionSettings); session.Start(); // 2. Pull an image var pullOp = session.PullImageAsync(new PullImageOptions("docker.io/library/alpine:latest")); pullOp.Progress = (op, progress) => Console.WriteLine($"Pull: {progress.Status} {progress.CurrentBytes}/{progress.TotalBytes}"); await pullOp; // 3. Configure an init process var initProcSettings = new ProcessSettings { CommandLine = new[] { "/bin/echo", "Hello from WSL Container!" }, OutputMode = ProcessOutputMode.Event }; // 4. Configure and create a container var containerSettings = new ContainerSettings("alpine:latest") { Name = "hello-container", InitProcess = initProcSettings }; var container = session.CreateContainer(containerSettings); // 5. Subscribe to init process events before starting var exited = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously); container.InitProcess.OutputReceived += data => Console.Write(Encoding.UTF8.GetString(data)); container.InitProcess.Exited += code => exited.TrySetResult(code); // 6. Start the container container.Start(); // 7. Wait for the init process to exit (30-second timeout) var completed = await Task.WhenAny(exited.Task, Task.Delay(TimeSpan.FromSeconds(30))); int exitCode = completed == exited.Task ? exited.Task.Result : -1; Console.WriteLine($"Process exited with code: {exitCode}"); // 8. Clean up if (container.State == ContainerState.Running) { container.Stop(Signal.SIGTERM, TimeSpan.FromSeconds(10)); } container.Delete(DeleteContainerOption.None); session.Terminate(); return exitCode; } }示例中值得注意的细节:
- 前置检查通过
WslcService.GetMissingComponents()完成(见 service-class/wslcservice.md),组件缺失时提示运行wsl --install; - 初始化进程通过
ProcessSettings配置,事件订阅(OutputReceived/Exited)必须在container.Start()之前完成,避免漏掉早期输出或退出事件; - 退出等待使用了 30 秒超时保护,避免进程异常时程序永久挂起;
- 清理顺序为:停止容器 → 删除容器 → 终止会话。
深入实现:Session 的底层调用链
从前文各方法中已经可以勾勒出Session的完整实现层次,这里做一个汇总:
| C#/WinRT 公开方法 | 底层原生 SDK 调用 | 说明 |
|---|---|---|
Start() | WslcCreateSession+WslcGetSessionTerminationEvent+WslcRegisterSessionCrashDumpCallback | 创建会话、注册终止等待与崩溃回调 |
Terminate() | WslcTerminateSession | 终止会话 |
CreateContainer() | 底层会话句柄派生容器对象 | 容器归属当前会话 |
PullImage()/PullImageAsync() | 会话句柄派生的镜像操作 | 异步版本携带ImageProgress进度 |
ImportImage()/LoadImage() | 会话句柄派生的导入/加载 | 分别对应 tarball 与归档文件 |
PushImage() | 会话句柄派生的推送 | 需要RegistryAuth |
Authenticate() | WslcSessionAuthenticate | 返回身份令牌 |
DeleteImage()/TagImage() | 会话句柄派生的镜像管理 | 按 name/Id 删除、附加 tag |
所有 WinRT 方法定义与签名均可对照 src/windows/WslcSDK/winrt/Session.h 查看,其完整实现位于 src/windows/WslcSDK/winrt/Session.cpp(共 400 余行),WinRT 接口契约(wslcsdk.idl)则定义了这些类型的投影规则。会话对象本身通过wil::unique_any持有原生句柄,Dispose()/析构路径会调用WslcReleaseSession释放,实现 RAII 式资源管理。
小结与进一步阅读
Session是 WSL 容器 C# 开发模型中的「总控台」:先用SessionSettings描述资源需求并构造,Start()拉起会话 VM,之后所有镜像、容器、卷操作都以它为宿主执行,最后通过Terminate()与Dispose()完成清理。掌握Session的同步/异步双轨 API 与事件模型,是编写健壮 WSL 容器应用的第一步。
进一步阅读:
- 容器对象操作:core-classes/container.md、core-classes/process.md
- 相关配置类:settings-classes/sessionsettings.md、settings-classes/containersettings.md
- 相关数据与枚举:data-classes/imageinfo.md、data-classes/imageprogress.md、enumerations/sessionterminationreason.md
- 委托与事件模型:delegates-and-events.md
- 完整生命周期示例:end-to-end-example.md
- 服务级入口与已知限制:service-class/wslcservice.md、known-gaps.md
【免费下载链接】WSLWindows Subsystem for Linux项目地址: https://gitcode.com/GitHub_Trending/ws/WSL
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考