2024年11月

1.简介

随着Unity6的发布,URP17也已经可以上手使用,相对旧的版本改动较大的是加入了

RenderGraph、STP、Foveated rendering、GPU Resident Drawer等功能,部分功能只需要开关参数即可使用,

而GRD更像是Gpu driven管线下的SRP Batches升级,RenderGraph相较于HDRP之前使用的版本换了一套API。

最大的不同是,使用URP17编写Feature时,必须依赖于RenderGraph进行编写,接下来就来介绍一下。

1.1 相关Demo

目前URP17比较容易找到的学习Demo如下:

2.RenderGraph

打开任意URP的示例场景查看,RenderGraphView上各图标含义如下:

  1. 说明这是一个外部置入的RenderTexture
  2. 红色方块说明存在写入操作
  3. 绿色方块指存在读取操作(红绿方块说明读写操作)
  4. 该图标说明标记了全局RenderTexture

而顶部表明当前渲染一帧的各个Pass,左侧是各类RT。

URP17同时保留了旧的Feature逻辑与RenderGraph逻辑(打开任意pass文件为例):

public classDistortTunnelPass_Tunnel : ScriptableRenderPass
{
classPassData
{
publicRenderer tunnelObject;publicMaterial tunnelMaterial;
}
#pragma warning disable 618, 672 //Type or member is obsolete, Member overrides obsolete member //Unity calls the Configure method in the Compatibility mode (non-RenderGraph path) public override voidConfigure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescripor)
{
}
//Unity calls the Execute method in the Compatibility mode public override void Execute(ScriptableRenderContext context, refRenderingData renderingData)
{
}
#pragma warning restore 618, 672 //Unity calls the RecordRenderGraph method to add and configure one or more render passes in the render graph system. public override voidRecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
}
}

参考时忽略掉Configure和Execute的逻辑,执行逻辑关注RecordRenderGraph函数。

2.1 操作方式的改变

在RenderGraph中,之前的RTHandle由于不在该系统中托管,进入RenderGraph的材质都需要调用API进行转换,

转换为RendeGraph的RT后,无需考虑释放操作:

RenderTextureDescriptor textureProperties = new RenderTextureDescriptor(Screen.width, Screen.height, RenderTextureFormat.Default, 0);
TextureHandle textureHandle
= UniversalRenderer.CreateRenderGraphTexture(renderGraph, textureProperties, "My texture", false);

相关文档:

https://docs.unity3d.com/Manual/urp/render-graph-create-a-texture.html

此外RenderGraph对于空调用的pass,也会剔除进行优化,使用者需要手动标记以防止被剔除。

2.1 RecordRenderGraph

在该函数内可组织渲染逻辑,pass相关的逻辑需放在对应的代码块中,例如:

using (var builder = renderGraph.AddRasterRenderPass<PassData>(passName, out_))
{
builder.UseTexture(rt1);
builder.SetRenderAttachment(resourceData.activeColorTexture,
0);

builder.SetRenderFunc
<PassData>((data, context) =>{
MaterialPropertyBlock materialPropertyBlock
= newMaterialPropertyBlock();
materialPropertyBlock.SetTexture(
"_BlitTexture", rt1);
materialPropertyBlock.SetVector(
"_BlitScaleBias", new Vector4(1, 1, 0, 0));

context.cmd.DrawProcedural(Matrix4x4.identity, material,
0, MeshTopology.Triangles, 3, 1, materialPropertyBlock);
});
}

URP提供了多种RenderPass,例如处理光栅化相关逻辑使用RasterRenderPass组织相关逻辑。

在RenderPass的代码块中可使用builder对象配置RenderTarget、标记材质的读写等

而具体的pass绘制逻辑则在SetRenderFunc代码块中。

RecordRenderGraph内可以调用多次AddRenderPass,但URP并没有整理旧API的代码和相关工具类,

以至于容易使用旧的API导致报错,这点需要注意。

3.编写Feature

3.1 Blit与SetTarget

从前有句俗话“切RT的性能消耗相当于半个pass”,Unity SRP在几个版本的升级都在逐渐强调不切RenderTarget直接绘制,

如Cockpit Demo的屏幕空间描边。

3.2 屏幕模糊Demo

下面通过屏幕模糊Demo案例,演示URP17下pass的编写。

通过外部EnqueuePass的方式,在场景中通过控制器脚本添加该Pass,

MyBlurSceneController.cs:

usingUnityEngine;usingUnityEngine.Rendering.Universal;usingUnityEngine.Rendering;public classMyBlurSceneController : MonoBehaviour
{
publicMaterial material;
[Range(
2, 15)] public int blurPasses = 3;
[Range(
0, 4)] public int downSample = 0;
[Range(
0.0f, 10f)] public float offset = 0.2f;public RenderPassEvent injectionPoint =RenderPassEvent.BeforeRenderingPostProcessing;public int injectionPointOffset = 0;public ScriptableRenderPassInput inputRequirements =ScriptableRenderPassInput.Color;public CameraType cameraType =CameraType.Game;privateMyBlurPass mMyBlurPass;private voidOnEnable()
{
SetupPass();

RenderPipelineManager.beginCameraRendering
+=OnBeginCamera;
}
private voidOnDisable()
{
RenderPipelineManager.beginCameraRendering
-=OnBeginCamera;
}
public virtual voidSetupPass()
{
mMyBlurPass
= newMyBlurPass();

mMyBlurPass.renderPassEvent
= injectionPoint +injectionPointOffset;
mMyBlurPass.material
=material;

mMyBlurPass.ConfigureInput(inputRequirements);
}
public virtual voidOnBeginCamera(ScriptableRenderContext ctx, Camera cam)
{
if (mMyBlurPass == null || material == null)return;if ((cam.cameraType & cameraType) == 0) return;

mMyBlurPass.blurPasses
=blurPasses;
mMyBlurPass.downSample
=downSample;
mMyBlurPass.offset
=offset;

cam.GetUniversalAdditionalCameraData().scriptableRenderer.EnqueuePass(mMyBlurPass);
}
}

MyBlurPass.cs:

usingUnityEngine;usingUnityEngine.Rendering.RenderGraphModule;usingUnityEngine.Rendering;usingUnityEngine.Rendering.Universal;usingUnityEngine.Rendering.RenderGraphModule.Util;public classMyBlurPass : ScriptableRenderPass
{
public classPassData
{
publicTextureHandle tempRt1;publicTextureHandle tempRt2;
}
publicMaterial material;
[Range(
2, 15)] public int blurPasses = 3;
[Range(
1, 4)] public int downSample = 1;
[Range(
0.0f, 10f)] public float offset = 0.2f;public override voidRecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
var resourceData = frameData.Get<UniversalResourceData>();var passData = newPassData();var w = Screen.width >>downSample;var h = Screen.height >>downSample;

RenderTextureDescriptor textureProperties
= new RenderTextureDescriptor(w, h, RenderTextureFormat.Default, 0);
passData.tempRt1
= UniversalRenderer.CreateRenderGraphTexture(renderGraph, textureProperties, "MyBlurPassTempRt1", false);

textureProperties
= new RenderTextureDescriptor(w, h, RenderTextureFormat.Default, 0);
passData.tempRt2
= UniversalRenderer.CreateRenderGraphTexture(renderGraph, textureProperties, "MyBlurPassTempRt2", false);var rt1 =passData.tempRt1;var rt2 =passData.tempRt2;//将屏幕RT Blit到rt1上 var para = new RenderGraphUtils.BlitMaterialParameters(resourceData.activeColorTexture, rt1, material, 0);
renderGraph.AddBlitPass(para,
"MyBlurPassBlitFirst");

material.SetFloat(
"_SampleOffset", offset);//模糊迭代 for (int i = 0; i < blurPasses - 1; ++i)
{
para
= new RenderGraphUtils.BlitMaterialParameters(rt1, rt2, material, 0);
renderGraph.AddBlitPass(para, $
"MyBlurPassBlit_{i}");var tmp =rt1;
rt1
=rt2;
rt2
=tmp;
}
//通过直接绘制的方式,将模糊RT绘制到屏幕上 using (var builder = renderGraph.AddRasterRenderPass<PassData>(passName, out_))
{
builder.UseTexture(rt1);
builder.SetRenderAttachment(resourceData.activeColorTexture,
0);

builder.SetRenderFunc
<PassData>((data, context) =>{
MaterialPropertyBlock materialPropertyBlock
= newMaterialPropertyBlock();
materialPropertyBlock.SetTexture(
"_BlitTexture", rt1);
materialPropertyBlock.SetVector(
"_BlitScaleBias", new Vector4(1, 1, 0, 0));

context.cmd.DrawProcedural(Matrix4x4.identity, material,
0, MeshTopology.Triangles, 3, 1, materialPropertyBlock);
});
}
}
}

接着在ShaderGraph中连出模糊的逻辑,注意Blit对应的参数_BlitTexture、_BlitScaleBias:

最后在场景中挂载控制器以及材质球,即可使用该模糊Pass。

一、基本概念

Java NIO 是 Java 1.4 引入的,用于处理高速、高并发的 I/O 操作。与传统的阻塞 I/O 不同,NIO 支持非阻塞 I/O 和选择器,可以更高效地管理多个通道。

二、核心组件

  1. 通道(Channel)
    • Channel
      是 NIO 中用于读取和写入数据的主要接口,提供双向数据传输的能力。
    • 常见的通道实现:
      • FileChannel
        :用于文件的读写操作。
      • SocketChannel
        :用于 TCP 网络通信。
      • ServerSocketChannel
        :用于监听 TCP 连接的服务器端通道。
      • DatagramChannel
        :用于 UDP 网络通信。
  2. 缓冲区(Buffer)
    • Buffer
      是 NIO 中用于存储数据的容器。与传统的流不同,NIO 通过缓冲区进行数据的读写。
    • 常见的缓冲区类型:
      • ByteBuffer
        :处理字节数据。
      • CharBuffer
        :处理字符数据。
      • IntBuffer

        LongBuffer
        等:处理整型和长整型数据。
    • 缓冲区有三个重要的属性:
      • position
        :当前缓冲区的读写位置。
      • limit
        :可以读取或写入的最大数据量。
      • capacity
        :缓冲区的总容量。
  3. 选择器(Selector)
    • Selector
      是 NIO 的核心组件之一,允许单个线程监控多个通道的事件。
    • 通过选择器,可以处理多个连接而不需要为每个连接都创建一个线程。
    • Selector 的工作流程:
      • 注册通道(Channel)到选择器。
      • 选择感兴趣的通道(如可读、可写、连接等)。
      • 处理就绪的通道。

三、底层实现

  1. 文件描述符

    NIO 底层仍然依赖操作系统的文件描述符。每个通道对应一个文件描述符,用于直接与操作系统进行交互。

  2. 事件驱动

    NIO 使用事件驱动的机制。选择器会调用操作系统的底层 API(如 epoll、kqueue)来获取就绪事件。这种机制允许线程在等待事件时处于睡眠状态,从而减少 CPU 资源的消耗。

四、设计原理

  1. 非阻塞 IO

    NIO 允许通道在没有可用数据时不阻塞线程。线程可以继续执行其他操作,适合处理高并发请求。

  2. 选择性处理

    使用选择器,可以选择性地处理就绪通道,避免了为每个连接创建一个线程的开销。

  3. 适应性强

    NIO 的设计使得它可以处理各种数据源(如文件、网络等),提高了灵活性。

五、底层原理

  1. 内存管理

    NIO 的缓冲区(Buffer)底层使用
    java.nio.HeapByteBuffer

    java.nio.DirectByteBuffer
    ,后者直接在 JVM 之外分配内存,减少了与 JVM 堆内存的交互开销,提升了 I/O 性能,特别是在大数据量传输时。

  2. 内存映射文件(Memory-Mapped File)

    NIO 的
    FileChannel
    支持内存映射文件,允许将文件映射到内存。这种方式使得文件内容可以像数组一样直接操作,大幅提升了文件读取和写入的速度,特别适用于大文件处理和高性能数据库实现。

  3. 选择器的实现

    选择器的实现通常基于操作系统提供的高效 I/O 多路复用机制,如 Linux 的
    epoll
    或 Windows 的
    IOCP
    。这些机制使得 NIO 能够在处理大量并发连接时表现优异。了解这些底层实现的机制,能够帮助开发者在不同操作系统上优化性能。

六、使用场景

  • 高性能 Web 服务器

    NIO 适合构建高性能的 Web 服务器,如 Netty 框架,利用其事件驱动和异步非阻塞的特性,可以处理数万并发连接,而不需要为每个连接创建一个线程。

  • 实时数据处理

    在需要实时处理大量数据的应用(如金融交易系统、在线游戏等),NIO 提供的低延迟和高吞吐量使其成为理想选择。

  • 跨平台的网络通信

    NIO 的通道和选择器机制提供了跨平台的网络通信能力,开发者可以轻松构建支持多种操作系统的网络应用。

  • 高并发网络应用

    NIO 适用于需要处理大量并发连接的应用,例如聊天服务器、HTTP 服务器和在线游戏等。

  • 异步文件处理

    使用
    AsynchronousFileChannel
    进行异步文件读写操作,适合需要高性能的文件处理场景。

七、性能特点

  1. 降低上下文切换

    NIO 的非阻塞特性降低了线程切换的开销,特别是在高并发情况下,提高了应用的吞吐量。

  2. 内存映射文件

    NIO 支持内存映射文件,可以将文件直接映射到内存,这种方式可以提高对大文件的访问速度。

  3. 减少资源占用

    由于使用选择器管理多个通道,NIO 可以减少对系统资源(如线程和内存)的占用,提高整体性能。

八、示例代码

以下是一个简单的 NIO 服务器示例,使用选择器处理客户端连接:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.SelectionKey;

public class NioServer {
    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(8080));
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            selector.select(); // 阻塞,直到有事件发生
            for (SelectionKey key : selector.selectedKeys()) {
                if (key.isAcceptable()) {
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(256);
                    int bytesRead = socketChannel.read(buffer);
                    if (bytesRead == -1) {
                        socketChannel.close();
                    } else {
                        buffer.flip();
                        // 处理数据...
                        socketChannel.write(buffer);
                    }
                }
            }
            selector.selectedKeys().clear(); // 清除已处理的事件
        }
    }
}

前言

公众号每月定期推广和分享的C#/.NET/.NET Core优秀项目和框架(每周至少会推荐两个优秀的项目和框架当然节假日除外),公众号推文中有项目和框架的介绍、功能特点、使用方式以及部分功能截图等(打不开或者打开GitHub很慢的同学可以优先查看公众号推文,文末一定会附带项目和框架源码地址)。注意:排名不分先后,都是十分优秀的开源项目和框架,每周定期更新分享。

MethodTimer

MaterialSkin

  • 项目简介:
    MaterialSkin是一个基于 Google 的 Material Design 风格开源、免费的.NET WinForms UI控件库,提供了一系列基于Material Design的UI控件,如复选框、单选按钮、列表视图等,使得开发者可以轻松地构建出符合Material Design规范的WinForms应用程序。
  • 项目源码地址:
    https://github.com/IgnaceMaes/MaterialSkin
  • 公众号详细介绍:
    https://mp.weixin.qq.com/s/5cQvqL9ryPe6OS1azmBdKg

Moq

  • 项目简介:
    Moq是一个.NET开源、流行、使用简单的 .NET 模拟库,充分利用了.NET 的 Linq 表达式树和 lambda 表达式。这使得 Moq 成为最具生产力、类型安全且支持重构的模拟库。它不仅支持模拟接口,还支持模拟类。其 API 非常简单直观,不需要任何关于模拟概念的事先知识或经验。从而简化单元测试中的依赖管理和验证过程,提高代码的可测试性和可维护性。
  • 项目源码地址:
    https://github.com/devlooped/moq
  • 公众号详细介绍:
    https://mp.weixin.qq.com/s/lJMf3UP1TQHAdE1gi9DWQw

TouchSocket

  • 项目简介:
    TouchSocket是一个整合性、功能丰富的.NET(包括 C# 、VB.Net、F#)网络通信框架。包含了socket、 tcp、udp、ssl、namedPipe、http、websocket、rpc、jsonrpc、webapi、xmlrpc、modbus等一系列的通信模块。一键式解决 tcp 黏分包问题,使用协议模板,可快速实现「固定包头」、「固定长度」、「区间字符」等一系列的数据报文解析。
  • 项目源码地址:
    https://gitee.com/RRQM_Home/TouchSocket
  • 公众号详细介绍:
    https://mp.weixin.qq.com/s/pOLG46N5fa-cSc8-1tlW7w

openai-dotnet

  • 项目简介:
    一个由 OpenAI 官方提供,旨在为 .NET 开发者提供方便的接口来访问 OpenAI 的 REST API。这个项目通过 NuGet 包的形式发布,使得 .NET 应用程序能够轻松地集成和使用 OpenAI 的各种 AI 模型和功能,如文本生成、图像生成、音频转写等。
  • 项目源码地址:
    https://github.com/openai/openai-dotnet
  • 公众号详细介绍:
    https://mp.weixin.qq.com/s/YaH-Ah59Aabt7AX_RclrbA

ZR.Admin.NET

PluginCore

  • 项目简介:
    PluginCore 是一个基于 ASP.NET Core 的轻量级插件框架,旨在简化插件的集成与管理。通过最少的配置,开发者可以快速集成并专注于业务逻辑的开发。它支持动态 WebAPI、插件隔离与共享、前后端分离、热插拔等特性,非常适合需要高度模块化与可扩展性的应用场景。
  • 项目源码地址:
    https://github.com/yiyungent/PluginCore
  • 公众号详细介绍:
    https://mp.weixin.qq.com/s/yOiBrFNLiZMwEUE5pzW5_Q

FileConverter

Eto.Forms

  • 项目简介:
    Eto.Forms是一个.NET开源、跨平台的桌面和移动应用的统一框架,该框架允许开发者使用单一的UI代码库构建在多个平台上运行的应用程序,并利用各自平台的原生工具包,从而使应用程序在所有平台上看起来和工作都像原生应用一样。
  • 项目源码地址:
    https://github.com/picoe/Eto
  • 公众号详细介绍:
    https://mp.weixin.qq.com/s/rknEF3jT568LbMsnCs3zqA

引言

随着技术的不断进步,微软的.NET 框架在每次迭代中都带来了令人惊喜的新特性。在.NET 9 版本中,一个特别引人注目的亮点是 AOT( Ahead-of-Time)支持,它允许开发人员将应用程序在编译阶段就优化为能够在老旧的 Windows 系统上运行,包括 Windows 7 和甚至 Windows XP。这不仅提升了性能,也为那些依然依赖这些老平台的企业和个人开发者提供了新的可能性。

小知识普及:

  1. NET 9 AOT 简介

.NET 9 的 AOT 编译器通过静态编译,将.NET 应用程序转换为可以直接在目标机器上执行的可执行文件,消除了在运行时的 JIT(Just-In-Time)编译所需的时间和资源。这对于对性能要求高且需要支持旧版系统的场景具有显著优势。

  1. 支持 Windows 7 与 Windows XP 的背景

尽管 Windows 7 和 XP 已经不再是主流操作系统,但它们在某些特定领域,如企业遗留系统、嵌入式设备或者资源受限的环境中仍有广泛应用。.NET 9 的 AOT 编译这一扩展,旨在满足这些场景的兼容性和性能需求。

  1. 如何实现
  • 编译过程优化
    :NET 9 在 AOT 编译时,对代码进行了更为细致的优化,使得生成的可执行文件更小,启动速度更快。
  • 向下兼容性
    :通过精心设计的编译策略,确保了对 Win7 及 XP API 的兼容性,使代码能够无缝运行。
  • 安全性考量
    :虽然支持老旧系统,但.NET 9 依然注重安全,提供了一定程度的保护机制以抵御潜在的风险。
  1. 实例应用与优势
  • 性能提升
    :AOT 编译后的程序通常比 JIT 执行的程序更快,尤其对于 CPU 密集型任务。
  • 部署简易
    :无需用户安装.NET 运行时,简化了部署流程。
  • 维护成本降低
    :对于依赖老旧系统的企业,避免了频繁升级运行时的困扰。

本文只在分享网友及站长实践的一个成果,如有更多发现,欢迎投稿或给本文PR。

Windows 7 支持

下图是网友编译的 Avalonia UI 跨平台项目在 Win 7 非 SP1 环境运行效果截图:

如上图,左侧是程序运行界面,右侧是操作系统版本。

为了便于读者代码拷贝,参考配置贴出如下:

<Project Sdk="Microsoft.NET.Sdk">
	<PropertyGroup>
		<OutputType>WinExe</OutputType>
		<TargetFramework>net9.0-windows</TargetFramework>
		<Nullable>enable</Nullable>
		<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
		<ApplicationManifest>app.manifest</ApplicationManifest>
		<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
		<PublishAot>true</PublishAot>
	</PropertyGroup>
	<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
		<InvariantGlobalization>true</InvariantGlobalization>
        <!--支持在Windows XP或更高版本的Windows操作系统上运行,XP下尝试Ava失败-->
		<WindowsSupportedOSPlatformVersion>5.1</WindowsSupportedOSPlatformVersion>
		<RuntimeIdentifier>win-x64</RuntimeIdentifier>
		<TargetPlatformMinVersion>5.1</TargetPlatformMinVersion>
	</PropertyGroup>
	<ItemGroup>
		<PackageReference Include="VC-LTL" Version="5.1.1-Beta3" />
	</ItemGroup>
	<ItemGroup>
		<PackageReference Include="Avalonia" Version="11.1.1" />
		<PackageReference Include="Avalonia.Desktop" Version="11.1.1" />
		<PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.1" />
		<PackageReference Include="Avalonia.Fonts.Inter" Version="11.1.1" />
		<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
		<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.1" />
		<PackageReference Include="Avalonia.ReactiveUI" Version="11.1.1" />
	</ItemGroup>
</Project>

上面关键配置说明:

  1. <PublishAot>true</PublishAot>

该开关用于支持AOT编译发布

  1. <WindowsSupportedOSPlatformVersion>5.1</WindowsSupportedOSPlatformVersion>

支持在Windows XP或更高版本的Windows操作系统上运行

  1. VC-LTL

VC-LTL是一个基于微软VC修改的开源运行时,有效减少应用程序体积并摆脱微软运行时DLL,比如msvcr120.dll、api-ms-win-crt-time-l1-1-0.dll等依赖。

Win7及以上版本,可能AOT就能正常运行(不需要安装.NET运行时)。但也有可能在目标系统运行失败,可添加该库尝试重新AOT编译。详细原理参考该仓库:
https://github.com/Chuyu-Team/VC-LTL

经站长实测:Windows7可能还需要添加YY-Thunks包引用:

<PackageReference Include="YY-Thunks" Version="1.1.4-Beta3" />

关于YY-Thunks:
链接
,说明:

众所周知,从 Windows 的每次更新又会新增大量 API,这使得兼容不同版本的 Windows 需要花费很大精力。导致现在大量开源项目已经不再兼容一些早期的 Windows 版本,比如 Windows XP RTM。

难道就没有一种快速高效的方案解决无法定位程序输入点的问题吗?

YY-Thunks(鸭船),存在的目的就是抹平不同系统的差异,编译时单纯添加一个 obj 即可自动解决这些兼容性问题。让你兼容旧版本 Windows 更轻松!

经测试,Winform 可以.NET 9 x86 AOT发布后运行,效果截图如下:

Winform 工程配置如下:

可拷贝配置如下:

<Project Sdk="Microsoft.NET.Sdk">
	<PropertyGroup>
		<OutputType>WinExe</OutputType>
		<TargetFramework>net9.0-windows</TargetFramework>
		<Nullable>enable</Nullable>
		<UseWindowsForms>true</UseWindowsForms>
		<ImplicitUsings>enable</ImplicitUsings>
	</PropertyGroup>
	<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
		<InvariantGlobalization>true</InvariantGlobalization>
		<WindowsSupportedOSPlatformVersion>5.1</WindowsSupportedOSPlatformVersion>
		<RuntimeIdentifier>win-x64</RuntimeIdentifier>
		<TargetPlatformMinVersion>5.1</TargetPlatformMinVersion>
		<PublishAot>true</PublishAot>
		<_SuppressWinFormsTrimError>true</_SuppressWinFormsTrimError>
	</PropertyGroup>
	<ItemGroup>
		<PackageReference Include="VC-LTL" Version="5.1.1-Beta3" />
		<PackageReference Include="WinFormsComInterop" Version="0.5.0" />
	</ItemGroup>
</Project>

入口再加一句代码
ComWrappers.RegisterForMarshalling(WinFormsComInterop.WinFormsComWrappers.Instance);

using System.Runtime.InteropServices;

namespace WinFormsAotDemo;

internal static class Program
{
    /// <summary>
    ///  The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        // To customize application configuration such as set high DPI settings or default font,
        // see https://aka.ms/applicationconfiguration.

        ComWrappers.RegisterForMarshalling(WinFormsComInterop.WinFormsComWrappers.Instance);

        ApplicationConfiguration.Initialize();
        Application.Run(new Form1());
    }
}

Windows XP 支持

目前测试可运行控制台程序:

网友得出结论:

XP 需要链接 YY-Thunks,参考链接:
https://github.com/Chuyu-Team/YY-Thunks(前面有提及,Win7如果失败也可以添加该包引用尝试)

大家可关注 YY-Thunks 这个 ISSUE:
https://github.com/Chuyu-Team/YY-Thunks/issues/66

控制台支持 XP 的工程配置如下:

<Project Sdk="Microsoft.NET.Sdk">
	<PropertyGroup>
		<OutputType>Exe</OutputType>
		<TargetFramework>net9.0</TargetFramework>
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
	</PropertyGroup>
	<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
		<InvariantGlobalization>true</InvariantGlobalization>
		<WindowsSupportedOSPlatformVersion>5.1</WindowsSupportedOSPlatformVersion>
		<SupportWinXP>true</SupportWinXP>
		<PublishAot>true</PublishAot>
	</PropertyGroup>
	<ItemGroup>
		<PackageReference Include="VC-LTL" Version="5.1.1-Beta3" />
	</ItemGroup>
</Project>

网友心得:

有待加强的部分

经测试Prism框架使用会报错:

使用HttpClient也会出错:

2024-08-02

通过阅读开源Avalonia主题库 [Semi.Avalonia](
irihitech/Semi.Avalonia: Avalonia theme inspired by Semi Design (github.com)
) 的源码及作者
Rabbitism
兔佬的PR已经解决Prism问题的,其它库问题使用方法应该类似,修改如下:

主工程添加Roots.xml,内容如下:

<linker>
    <assembly fullname="CodeWF.Toolbox.Desktop" preserve="All"/>
    <assembly fullname="Ursa.PrismExtension" preserve="All" />
    <assembly fullname="Prism" preserve="All" />
    <assembly fullname="DryIoc" preserve="All" />
    <assembly fullname="Prism.Avalonia" preserve="All"/>
    <assembly fullname="Prism.DryIoc.Avalonia" preserve="All"/>
    <assembly fullname="CodeWF.Toolbox" preserve="All" />
</linker>

主工程添加该XML配置:

<ItemGroup>
    <TrimmerRootDescriptor Include="Roots.xml" />
</ItemGroup>

HttpClient也是类似的处理方法,这里不赘述,需要你进行更多尝试。

每个公司的不同项目都是极其不同、复杂的,实际发布还需要不断测试,为了支持Windows7、Windows XP可能不得不做出使用库替换、部分API使用取舍等操作,欢迎读者将使用过程中的心得体会进行分享。

结语

.NET 9 的 AOT 支持无疑拓宽了.NET 生态的应用范围,为那些需要在老旧平台上运行高性能应用的开发者提供了强大的工具。随着技术的发展,我们期待未来更多的.NET 版本能够进一步打破界限,让编程变得更加灵活和高效。

感谢网友
GSD

M$達
分享的这个好消息,大石头这篇文章《各版本操作系统对.NET 支持情况》推荐大家阅读:
https://newlifex.com/tech/os_net

参考AOT项目:
https://github.com/dotnet9/CodeWF.Toolbox

技术交流

软件开发技术交流添加 QQ 群:771992300

或扫站长微信(
codewf
,备注
加群
)加入微信技术交流群:

C# 异步编程中
await
实现原理详解

在C#中,
async

await
关键字用于编写异步代码。本文将详细介绍
await
的实现原理,包括状态机的生成、回调函数的注册和触发等关键步骤。

1. 异步方法的基本概念

在C#中,
async
关键字标记一个方法为异步方法,而
await
关键字用于等待一个异步操作完成。异步方法可以提高程序的响应性和性能,特别是在处理I/O操作和网络请求时。

2. 示例异步方法

我们以一个简单的异步方法为例,来详细解释
await
的实现原理。

public class Example
{
    public async Task<int> CalculateAsync()
    {
        int a = await Task.Run(() => 10);
        int b = await Task.Run(() => 20);
        return a + b;
    }
}

3. 编译器生成的状态机

编译器会为每个异步方法生成一个状态机。状态机是一个结构体,包含了异步方法的所有局部变量和状态信息。

编译器生成的状态机类

public class Example
{
    public Task<int> CalculateAsync()
    {
        <CalculateAsync>d__0 stateMachine = new <CalculateAsync>d__0();
        stateMachine.<>4__this = this;
        stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
        stateMachine.<>1__state = -1;
        stateMachine.<>t__builder.Start(ref stateMachine);
        return stateMachine.<>t__builder.Task;
    }

    [StructLayout(LayoutKind.Auto)]
    [AsyncMethodBuilder(typeof(AsyncTaskMethodBuilder<int>))]
    private struct <CalculateAsync>d__0 : IAsyncStateMachine
    {
        public int <>1__state;
        public AsyncTaskMethodBuilder<int> <>t__builder;
        public Example <>4__this;
        public int <a>5__1;
        public TaskAwaiter<int> <>u__1;

        private void MoveNext()
        {
            int num = <>1__state;
            try
            {
                TaskAwaiter<int> awaiter;
                switch (num)
                {
                    case 0:
                        goto TR_0000;
                    case 1:
                        <>1__state = -1;
                        awaiter = <>u__1;
                        <>u__1 = default(TaskAwaiter<int>);
                        goto TR_0001;
                    case 2:
                        <>1__state = -1;
                        break;
                    default:
                        <>1__state = 0;
                        awaiter = Task.Run<int>(() => 10).GetAwaiter();
                        if (!awaiter.IsCompleted)
                        {
                            num = (<>1__state = 0);
                            <>u__1 = awaiter;
                            <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
                            return;
                        }
                        goto TR_0000;
                }
                TR_0000:
                <a>5__1 = awaiter.GetResult();
                awaiter = Task.Run<int>(() => 20).GetAwaiter();
                if (!awaiter.IsCompleted)
                {
                    num = (<>1__state = 1);
                    <>u__1 = awaiter;
                    <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
                    return;
                }
                TR_0001:
                int b = awaiter.GetResult();
                int result = <a>5__1 + b;
                <>1__state = -2;
                <>t__builder.SetResult(result);
            }
            catch (Exception exception)
            {
                <>1__state = -2;
                <>t__builder.SetException(exception);
            }
        }

        [DebuggerHidden]
        private void SetStateMachine(IAsyncStateMachine stateMachine)
        {
        }
    }
}

4. 实现流程详解

初始化状态机


CalculateAsync
方法中,创建状态机实例
<CalculateAsync>d__0

<CalculateAsync>d__0 stateMachine = new <CalculateAsync>d__0();
stateMachine.<>4__this = this;
stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
stateMachine.<>1__state = -1;
  • <>4__this
    :指向当前实例,即
    Example
    类的实例。
  • <>t__builder
    :创建
    AsyncTaskMethodBuilder<int>
    实例,用于管理任务的生命周期。
  • <>1__state
    :初始化状态为
    -1
    ,表示方法尚未开始执行。

开始执行

调用
Start
方法开始执行异步方法。
Start
方法会调用状态机的
MoveNext
方法。

stateMachine.<>t__builder.Start(ref stateMachine);

执行方法体


MoveNext
方法中,根据当前状态
<>1__state
执行相应的代码。

private void MoveNext()
{
    int num = <>1__state;
    try
    {
        TaskAwaiter<int> awaiter;
        switch (num)
        {
            // 处理不同的状态
        }
    }
    catch (Exception exception)
    {
        <>1__state = -2;
        <>t__builder.SetException(exception);
    }
}

遇到
await

遇到第一个
await
关键字时,调用
Task.Run(() => 10).GetAwaiter()
获取
Awaiter
对象。

awaiter = Task.Run<int>(() => 10).GetAwaiter();
  • 检查
    awaiter.IsCompleted
    ,如果任务已经完成,直接调用
    awaiter.GetResult()
    获取结果。
  • 如果任务未完成,记录当前状态
    <>1__state
    ,保存
    awaiter
    对象,并调用
    <>t__builder.AwaitUnsafeOnCompleted
    注册回调。
if (!awaiter.IsCompleted)
{
    num = (<>1__state = 0);
    <>u__1 = awaiter;
    <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
    return;
}

注册回调

AwaitUnsafeOnCompleted
方法会注册一个回调,当任务完成时,回调会被触发。

public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
    where TAwaiter : ICriticalNotifyCompletion
    where TStateMachine : IAsyncStateMachine
{
    awaiter.UnsafeOnCompleted(stateMachine.MoveNext);
}
  • awaiter.UnsafeOnCompleted
    方法注册一个回调函数,该回调函数会在任务完成时被触发。
  • stateMachine.MoveNext
    是一个委托,指向状态机的
    MoveNext
    方法。

任务完成

当任务完成时,回调会被触发,重新调用
MoveNext
方法,恢复异步方法的执行。

public void OnCompleted(Action continuation)
{
    task.ContinueWith(_ => continuation(), TaskScheduler.Default);
}

继续执行

从上次暂停的地方继续执行方法体。

TR_0000:
<a>5__1 = awaiter.GetResult();
awaiter = Task.Run<int>(() => 20).GetAwaiter();
if (!awaiter.IsCompleted)
{
    num = (<>1__state = 1);
    <>u__1 = awaiter;
    <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
    return;
}
  • 遇到第二个
    await
    关键字时,重复上述步骤。

方法完成

当所有异步操作完成并计算出结果后,设置状态
<>1__state

-2
,表示方法已经完成。

int b = awaiter.GetResult();
int result = <a>5__1 + b;
<>1__state = -2;
<>t__builder.SetResult(result);
  • 调用
    <>t__builder.SetResult
    设置任务的结果。
  • 如果在执行过程中抛出异常,捕获异常并调用
    <>t__builder.SetException
    设置任务的异常。
catch (Exception exception)
{
    <>1__state = -2;
    <>t__builder.SetException(exception);
}

5. 深入理解
AsyncTaskMethodBuilder

AsyncTaskMethodBuilder
是一个辅助类,用于构建和管理异步方法的任务。它提供了以下方法:

  • Create
    :创建一个新的
    AsyncTaskMethodBuilder
    实例。
  • Start
    :开始执行异步方法,调用状态机的
    MoveNext
    方法。
  • AwaitUnsafeOnCompleted
    :注册回调函数,当任务完成时触发回调。
  • SetResult
    :设置任务的结果。
  • SetException
    :设置任务的异常。

AsyncTaskMethodBuilder
的内部实现

AsyncTaskMethodBuilder
内部维护了一个
Task
对象,用于表示异步操作的结果。当异步方法完成时,
SetResult
方法会设置任务的结果,
SetException
方法会设置任务的异常。

public struct AsyncTaskMethodBuilder<TResult>
{
    private Task<TResult> task;

    public static AsyncTaskMethodBuilder<TResult> Create()
    {
        return new AsyncTaskMethodBuilder<TResult>(new Task<TResult>());
    }

    private AsyncTaskMethodBuilder(Task<TResult> task)
    {
        this.task = task;
    }

    public void Start<TStateMachine>(ref TStateMachine stateMachine)
        where TStateMachine : IAsyncStateMachine
    {
        stateMachine.MoveNext();
    }

    public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : INotifyCompletion
        where TStateMachine : IAsyncStateMachine
    {
        awaiter.OnCompleted(stateMachine.MoveNext);
    }

    public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : ICriticalNotifyCompletion
        where TStateMachine : IAsyncStateMachine
    {
        awaiter.UnsafeOnCompleted(stateMachine.MoveNext);
    }

    public void SetResult(TResult result)
    {
        task.SetResult(result);
    }

    public void SetException(Exception exception)
    {
        task.SetException(exception);
    }

    public Task<TResult> Task => task;
}

6. 异步方法的生命周期

异步方法的生命周期可以分为以下几个阶段:

  1. 初始化
    :创建状态机实例,初始化状态和任务构建器。
  2. 开始执行
    :调用
    Start
    方法开始执行异步方法。
  3. 执行方法体
    :在
    MoveNext
    方法中,根据当前状态执行相应的代码。
  4. 遇到
    await

    :检查任务是否完成,如果未完成则注册回调并暂停方法执行。
  5. 任务完成
    :回调被触发,重新调用
    MoveNext
    方法,恢复异步方法的执行。
  6. 方法完成
    :所有异步操作完成,设置任务的结果或异常。

7. 异步方法的优势

使用
async

await
编写的异步方法有以下优势:

  • 提高响应性
    :异步方法不会阻塞主线程,应用程序可以继续响应用户的输入和其他事件。
  • 提高性能
    :异步方法可以并发执行多个任务,充分利用系统资源。
  • 简化代码
    :异步方法的代码结构类似于同步方法,易于理解和维护。

8. 异步方法的注意事项

尽管
async

await
提供了许多优势,但在使用时也需要注意以下几点:

  • 避免
    async void


    async void
    方法主要用于事件处理程序,其他情况下应避免使用,因为它无法被等待,并且异常处理较为困难。
  • 异常处理
    :异步方法中的异常会被包装在
    AggregateException
    中,需要特殊处理。
  • 资源管理
    :异步方法中使用
    using
    语句时,需要注意
    Dispose
    方法的调用时机。

9. 完整的流程图

为了更好地理解这个过程,可以用流程图来展示:

总结

通过上述详细的解释和示例代码,我们可以总结出以下几点:

  1. 异步方法的基本概念

    async

    await
    关键字用于编写异步代码。
  2. 状态机的生成
    :编译器为每个异步方法生成一个状态机,包含所有局部变量和状态信息。
  3. MoveNext
    方法的执行


    MoveNext
    方法是状态机的核心,负责管理和执行异步操作。
  4. 回调函数的注册和触发

    • 当遇到
      await
      关键字时,编译器会生成代码来检查任务是否已经完成。
    • 如果任务未完成,注册回调并暂停方法执行。
    • 当任务完成时,回调函数会被触发,重新调用状态机的
      MoveNext
      方法,从而恢复异步方法的执行。
  5. AwaitUnsafeOnCompleted
    方法的作用

    :在任务完成时注册一个回调函数,回调函数会在任务完成后被触发,从而恢复异步方法的执行。

希望这些解释能帮助你更好地理解
await
实现原理。如果你还有任何疑问,请随时提问!
详情请看:
https://www.cnblogs.com/Bob-luo/p/18518463

希望这篇文章对你有所帮助!如果你有任何进一步的问题或需要更多的细节,请告诉我。