2024年2月

ReadOnlySpan<T>
可以说现在高性能操作的重要基石

其原理有兴趣的同学可以看 2018 的介绍
Span<T>
文章

其为了保障大家安全使用做了相应的限制

那么有没方法绕过呢?

在class中持有 ReadOnlySpan

直接持有是不可能的,本身为
ref struct
就保障了大家写不出持有它的代码

但是我们可以玩骚操作,无法持有你,我们可以创造一个一模一样的你

如下面代码,我们获取span 对应的指针

public unsafe class ReadOnlySpanReaderBuffer<T> 
{
    internal void* _buffer;
    internal int _length;

    public ReadOnlySpanReaderBuffer(Span<T> span)
    {
        _buffer = Unsafe.AsPointer(ref span.GetPinnableReference());
        _length = span.Length;
    }

    public ReadOnlySpanReaderBuffer(ReadOnlySpan<T> span)
    {
        _buffer = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span));
        _length = span.Length;
    }

在需要使用的时候通过指针重新创建一个一模一样的span

public ReadOnlySpan<T> Readed
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    get => new ReadOnlySpan<T>(_buffer, _length);
}

将 Span
转换成 Memory

同样出于安全考虑,默认
Span<T>
无法转换成
Memory<T>

但是我们可以玩骚操作,无法转换你,我们可以创造一个

首先我们需要建立一个Memory的基础类, 通过它来告诉
Memory<T>
如何拿去我们从
Span<T>
里面偷出来的指针

public sealed unsafe class UnmanagedMemoryManager<T> : MemoryManager<T>
{
    private readonly T* _pointer;
    private readonly int _length;

    public UnmanagedMemoryManager(Span<T> span)
    {
        fixed (T* ptr = &MemoryMarshal.GetReference(span))
        {
            _pointer = ptr;
            _length = span.Length;
        }
    }

    public UnmanagedMemoryManager(T* pointer, int length)
    {
        if (length < 0) throw new ArgumentOutOfRangeException(nameof(length));
        _pointer = pointer;
        _length = length;
    }

    public UnmanagedMemoryManager(nint pointer, int length) : this((T*)pointer.ToPointer(), length) { }

    public override Span<T> GetSpan() => new Span<T>(_pointer, _length);

    // 一切的关键就在这个方法
    public override MemoryHandle Pin(int elementIndex = 0)
    {
        if (elementIndex < 0 || elementIndex >= _length)
            throw new ArgumentOutOfRangeException(nameof(elementIndex));
        return new MemoryHandle(_pointer + elementIndex);
    }

    public override void Unpin() { }

    protected override void Dispose(bool disposing) { }
}

在需要使用的时候通过指针重新创建一个一模一样的Memory

public ReadOnlyMemory<T> ReadedMemory
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    get => new UnmanagedMemoryManager<T>((IntPtr)_buffer, _length).Memory;
}

道路千万条,安全第一条,大家慎用骚操作啊

介绍

FastWiki
项目是一个高性能、基于最新技术栈的知识库系统,专为大规模信息检索和智能搜索设计。利用微软Semantic Kernel进行深度学习和自然语言处理,结合.NET 8和
MasaBlazor
前端框架,后台采用
.NET 8
+
MasaFramework
+
SemanticKernel
,实现了一个高效、易用、可扩展的智能向量搜索平台。我们的目标是提供一个能够理解和处理复杂查询的智能搜索解决方案,帮助用户快速准确地获取所需信息。

技术栈

  • 前端框架:
    MasaBlazor
    通过
    HttpClient
    实现前后分离
  • 后端框架:
    MasaFramework
    基于 .NET 8使用
    MiniApis
    实现
    webApi
    功能,并且更高的性能
  • 向量搜索引擎:使用
    PostgreSQL
    的向量插件,优化搜索性能
  • 深度学习与NLP:微软Semantic Kernel,提升搜索的语义理解能力
  • 许可证:Apache-2.0,鼓励社区贡献和使用

特点

  • 智能搜索:借助Semantic Kernel的深度学习和自然语言处理技术,能够理解复杂查询,提供精准的搜索结果。
  • 高性能:通过pgsql的向量插件优化向量搜索性能,确保即使在大数据量下也能快速响应。
  • 现代化前端:使用MasaBlazor前端框架,提供响应式设计和用户友好的界面。
  • 强大的后端:基于最新的.NET 8和MasaFramework,确保了代码的高效性和可维护性。
  • 开源和社区驱动:采用Apache-2.0许可证,鼓励开发者和企业使用和贡献。

项目部分截图介绍

添加知识库:

上传我们的文档:

点击上传或将文档推动到此处(暂仅支持md或txt等文本文件后续会支持pdf等格式)

上传我们的文档下面提供我们的文档模板:

# 为什么选择 MASA Blazor?

## Blazor 是什么?

Blazor 是一个使用 .NET 生成交互式客户端 Web UI 的框架:

- 使用 C# 代替 JavaScript 来创建信息丰富的交互式 UI。
- 共享使用 .NET 编写的服务器端和客户端应用逻辑。
- 将 UI 呈现为 HTML 和 CSS,以支持众多浏览器,其中包括移动浏览器。
- 与新式托管平台(如 Docker)集成。
  使用 .NET 进行客户端 Web 开发可提供以下优势:
- 使用 C# 代替 JavaScript 来编写代码。
- 利用现有的 .NET 库生态系统。
- 在服务器和客户端之间共享应用逻辑。
- 受益于 .NET 的性能、可靠性和安全性。
- 在 Windows、Linux 和 macOS 上使用 Visual Studio 保持高效工作。
- 以一组稳定、功能丰富且易用的通用语言、框架和工具为基础来进行生成。

## MASA Blazor 是什么?

基于Material Design设计和BlazorComponent的交互能力提供标准的基础组件库。提供如布局、弹框标准、Loading、全局异常处理等标准场景的预置组件。从更多实际场景出发,满足更多用户和场景的需求,缩短开发周期,提高开发效率,并提供一整套Web解决方案 - MASA Blazor Pro。

## 为什么选择 MASA Blazor?

MASA Blazor 基于 Material 设计规范,每一个组件都经过精心设计,具有模块化、响应式和优秀的性能。

MASA Blazor 是由一支专业的全职技术团队进行定期维护升级,高效的响应速度,多元化的解决方案,并提供企业级支持。目前已在知名企业使用,且MASA团队自研的MASA Stack产品线也将持续使用,除了可以保证项目质量,还可以持续的增加新的组件和功能。

MASA Stack 除了为开发者提供众多中台类开源项目,其最基础的组成部分之一 MASA Blazor 也希望可以打造成最实用的组件库。

## Masa Blazor和Token有什么关系?

Token是一个01年的.NET程序员,热爱开源,Token经常对于Masa的开源项目进行贡献开源代码,他们的渊源就像是上天指定的一样,Masa Blazor是一个非常不错的开源项目哦。

## 优势:

- 丰富组件:包含Vuetify 1:1还原的基础组件,以及很多实用的预置组件和.Net深度集成功能,包括Url、面包屑、导航三联动,高级搜索,i18n等。
- UI设计语言:设计风格现代,UI 多端体验设计优秀。
- 专业示例:MASA Blazor Pro提供多种常见场景的预设布局。
- 简易上手:丰富详细的上手文档,免费的视频教程(制作中)。
- 社区活跃鼓励:用户参与实时互动,做出贡献加入我们,构建最开放的开源社区。
- 长期支持:全职团队维护,并提供企业级支持。

点击下一步数据处理:

在这里我们提供了直接拆分和问答拆分(暂未实现)

我们修改了自定义处理的参数,这个数值会影响到文档拆分,如果拆分得当回复的效果会更好,如果拆分过大会导致token消耗太大可能导致欠费,然后我们再点击下一步:

在这里我们可以看到所有的需要上传的文件,在这里的步骤是先将文件上传到服务器,然后会将数据添加到后台,并且进行向量化数据,这个过程会比较长具体看文档内容。

上传完成以后列表会显示上传的数据,点击查看可以查看到拆分的文档的所有数据

点击查看详情能看到所有的信息

点击应用->创建应用:

打开应用然后店家选择知识库,将刚刚添加的知识库于当前应用绑定,然后点击保存修改,,这样对话的时候就会搜索绑定的知识库了,在这里我们也可以修改一些应用参数,比如开场白或角色的prompt定义:

然后我们点击聊天,然后输入我们的知识库的内容

问:Masa Blazor和Token有什么关系?

我们可以看到以上知识库的回复效果,如果你是直接提问Gpt的话就不把你知道Token是谁!
知识库再一定情况下能弥补AI的欠缺,再比如定制我们的企业文档的时候只需要吧文档全部给知识库然后将应用做成一个对话我们就可以为客户提供一个体验更好的文档小助手,甚至于可以替代非常多的客服人力成本,如果你有更深的定制需求可以联系我微信:hjl010426

项目开源

FastWiki
采用Apache-2.0,您也可以完全商用不会有任何版权纠纷

Github:
https://github.com/239573049/fast-wiki
Gitee:
https://gitee.com/hejiale010426/fast-wiki

FastWiki
技术交流群:

前言

.NET应用如何防止被反编译?这个对于我们.NET开发而言是一个值得关注和重视的问题,防止应用程序被反编译的手段有很多本文我们主要讲讲如何使用ConfuserEx .NET开源免费的代码混淆工具保护你的.NET应用程序。

ConfuserEx .NET混淆工具介绍

ConfuserEx是一个功能强大且广泛使用的.NET代码混淆工具。它支持多种混淆技术,包括控制流混淆、字符串加密、资源加密等。它具有灵活的配置选项,可以根据不同的需求进行定制。

注意注意:不足的是目前只支持.NET Framework 2.0/3.0/3.5/4.0/4.5/4.6/4.7/4.8,不支持.NET Core代码混淆,本章.NET版本代码示例使用的是.NET Fx4.7.2。

三款免费的.NET混淆工具推荐

需要支持.NET Core代码混淆的工具可以看下面这篇文章中介绍的几款免费工具。

https://mp.weixin.qq.com/s/hXGRdQjC7NXiGwq84KbEnA

.NET反编译相关的文章

ConfuserEx .NET混淆工具安装

ConfuserEx-GUI.zip包解压即可使用:

使用ConfuserEx工具混淆.NET Fx .dll文件

添加需要混淆的.dll文件

将待混淆的.dll文件拖拽进中间方框区域(Drag input modules here),如下图所示:

设置混淆规则

选择Settings项,添加混淆规则,如下图所示:

设置混淆规则:Protections选择anti ildasm,应该是防止IL反编译。因为Ildasm.exe是微软提供的.NET的IL反编译器。

选择Proect!选项开始混淆

点击【Protect!】,就开始混淆了,Finished代表混淆完成并成功。

混淆成功保存的文件目录:

混淆前后反编译代码对比

混淆之前反编译结果:

混淆之后反编译结果:

一、用ILSpy无法打开:

二、用.NET Reflector反编译结果:

使用ConfuserEx工具混淆.NET Fx 混淆.exe文件

添加需要混淆的.exe文件

选择Settings选项卡,添加混淆规则

注意:这里与.dll文件设置混淆规则不一样,需要要设置Packer,压缩打包,且不需要编辑规则。

选择Proect!选项开始混淆

点击【Protect!】,就开始混淆了,Finished代表混淆完成并成功。

ILSpy查看混淆前后的.exe文件对比

混淆之前:

混淆之后:

运行混淆后的.exe文件

DotNetGuide技术社区交流群

  • DotNetGuide技术社区是一个面向.NET开发者的开源技术社区,旨在为开发者们提供全面的C#/.NET/.NET Core相关学习资料、技术分享和咨询、项目推荐、招聘资讯和解决问题的平台。
  • 在这个社区中,开发者们可以分享自己的技术文章、项目经验、遇到的疑难技术问题以及解决方案,并且还有机会结识志同道合的开发者。
  • 我们致力于构建一个积极向上、和谐友善的.NET技术交流平台,为广大.NET开发者带来更多的价值和成长机会。





欢迎加入DotNetGuide技术社区微信交流群

wmproxy

wmproxy
已用
Rust
实现
http/https
代理,
socks5
代理, 反向代理, 负载均衡, 静态文件服务器,
websocket
代理,四层TCP/UDP转发,内网穿透等,会将实现过程分享出来,感兴趣的可以一起造个轮子

项目地址

国内: https://gitee.com/tickbh/wmproxy

github: https://github.com/tickbh/wmproxy

设计目标

证书的自动续期,让系统免除证书过期的烦恼,保证系统的正确运行。

关于证书的验证

证书的组成部分:公钥,私钥

公钥部分

公开的信息cert,也称公钥,在nginx体系中通常以
.pem
结尾

Cert,作为“Certificate”(证书)的缩写,通常用于表示网络安全和加密领域中的数字证书。这些证书是用于证明身份和保障安全性的重要工具,包含了许多关键信息。

一般来说,证书中存放的信息主要包括:

  1. 证书颁发机构(Certificate Authority,CA)的信息:这包括CA的名称、公钥和证书颁发者的数字签名等。这些信息用于验证证书的合法性和真实性。
  2. 证书持有者的信息:这通常包括组织或个人的名称、域名、公钥和证书持有者的数字签名等。这些信息用于标识证书的所有者和验证其身份。
  3. 证书的有效期:证书通常有一个有效期限,包括开始日期和结束日期。这用于确定证书是否仍在有效期内。

此外,证书中还可能包含其他信息,例如证书的序列号、扩展字段等。这些信息对于特定的应用场景可能具有重要意义。

总之,Cert中存放的信息是数字证书的重要组成部分,对于保障网络安全和身份认证具有重要意义。

私钥部分

服务器专用的信息,称为私钥,在nginx体系中通常以
.key
结尾

私钥的主要作用是在TLS加密通信过程中,对从服务器发送到客户端的数据进行加密,以确保数据的机密性和安全性。当客户端向服务器发送请求时,服务器会使用其私钥对响应数据进行加密,然后发送给客户端。客户端在接收到加密数据后,会使用服务器公钥进行解密,从而获取到原始数据。

由于私钥的非公开性,如果私钥被泄露,将会对TLS加密通信的安全性造成严重威胁。因此,私钥的生成、存储和使用都需要遵循严格的安全标准和最佳实践。通常,私钥应该在安全的环境中生成,并且只由授权的人员管理和使用。

在TLS证书的生命周期中,私钥的管理和使用也是非常重要的。一旦私钥丢失或泄露,就需要重新生成新的密钥对和证书,以确保加密通信的安全性。因此,对于TLS证书的私钥部分,必须采取严格的安全措施,以确保其机密性和安全性。

证书无效的可能

SSL证书可能会因为多种原因而无效。以下是一些常见的情况:

  1. 证书过期
    :SSL证书有有效期限,一旦过期,浏览器会拒绝连接并显示证书无效的警告。为了避免这种情况,管理员需要定期检查证书的到期日期,并在必要时进行更新或续订。
  2. 域名不匹配
    :SSL证书是针对特定的域名颁发的,如果证书中的域名与实际访问的域名不匹配,浏览器也会显示证书无效。这可能是因为证书是为另一个域名颁发的,或者证书中包含的域名拼写错误。
  3. 证书链不完整
    :SSL证书通常依赖于一个证书颁发机构(CA)的证书链。如果证书链中的任何证书丢失或损坏,浏览器可能无法验证证书的有效性,并显示证书无效。
  4. 浏览器不受信任
    :如果证书颁发机构(CA)的证书被浏览器标记为不受信任或被撤销,那么使用该CA颁发的SSL证书也将被视为无效。

此篇中主要介绍证书过期如何维护的可能。

获取过期时间

关于tls的处理库,这里选择的是
rustls
,查询其相关Api及源码,发现其并未提供Cert的过期时间。这里选择用第三方库
x509-certificate
来获取证书的过期时间,他并不依赖于openssl,可以在不加载openssl的情况下获取到证书的过期时间。

api相关函数:

pub fn validity_not_after(&self) -> DateTime<Utc>

// Obtain the certificate validity “not after” time.

设计要点

  1. 区分是否为acme的证书(只有acme证书才能自动获取)
  2. 读取证书的时候获取过期时间
  3. 在接受证书时判断是否过期
    1. 未过期,直接继续执行
    2. 将过期或者已过期未加载,请求新的证书
    3. 已有新的证书,进行加载
    4. 保证不会频繁加载
  4. 用有效的证书进行tls操作

源码相关设计

新设计类

/// 包装tls accepter, 用于适应acme及自有证书两种
#[derive(Clone)]
pub struct WrapTlsAccepter {
    /// 最后请求的时间
    pub last: Instant,
    /// 最后成功加载证书的时间
    pub last_success: Instant,
    /// 域名
    pub domain: Option<String>,
    pub accepter: Option<TlsAcceptor>,
    /// 证书的过期时间,将加载证书的时候同步读取
    pub expired: Option<DateTime<Utc>>,
    pub is_acme: bool,
}

添加最后成功加载的时间,与全局的加载成功时间做比对。

lazy_static! {
    // 成功加载时间记录,以方便将过期的数据做更新
    static ref SUCCESS_CERT: Mutex<HashMap<String, Instant>> = Mutex::new(HashMap::new());
}

判断是否即将到期,到期前一天将自动更新

fn is_tls_will_expired(&self) -> bool {
    if let Some(expire) = &self.expired {
        let now = Utc::now();
        if now.timestamp() > expire.timestamp() - 86400 {
            return true;
        }
    }
    false
}

将过期时将重新触发加载:

if self.is_acme && self.is_tls_will_expired() {
    let _ = self.check_and_request_cert();
}

#[cfg(feature = "acme-lib")]
fn check_and_request_cert(&self)  -> Result<(), Error> {
    if self.domain.is_none() {
        return Err(io::Error::new(io::ErrorKind::Other, "未知域名").into());
    }
    {
        let mut map = CACHE_REQUEST
            .lock()
            .map_err(|_| io::Error::new(io::ErrorKind::Other, "Fail get Lock"))?;
        if let Some(last) = map.get(self.domain.as_ref().unwrap()) {
            if last.elapsed() < self.get_delay_time() {
                return Err(io::Error::new(io::ErrorKind::Other, "等待上次请求结束").into());
            }
        }
        map.insert(self.domain.clone().unwrap(), Instant::now());
    };

    let obj = self.clone();
    std::thread::spawn(move || {
        let _ = obj.request_cert();
    });
    Ok(())
}

最后在加载成功后,下一轮的处理中将尝试的加载ssl证书

pub fn update_last(&mut self) {
    if self.accepter.is_none() {
        if self.last.elapsed() > Duration::from_secs(5) {
            self.try_load_cert();
            self.last = Instant::now();
        }
    } else {
        if self.domain.is_none() {
            return;
        }
        let map = SUCCESS_CERT.lock().unwrap();
        let doamin = &self.domain.clone().unwrap();
        if !map.contains_key(doamin) {
            return;
        }
        if self.last_success < map[doamin] && self.last < map[doamin] {
            self.try_load_cert();
            self.last = map[doamin];
        }
    }
}

如此一个拥有自动请求且自动更新的acme请求已完成。
如果有细心的已发现相关代码用了feature,基本上等于Cpp中的
#ifdef xxx
也是用来控制代码是否启用相关的。

关于条件编译 Features

Cargo Feature 是非常强大的机制,可以为大家提供条件编译和可选依赖的高级特性。

相关链接可以参考
features

将其中的依赖改成了

acme-lib = { version = "^0.9.1", default-features = true, optional = true}
openssl = { version = "0.10.32", default-features = false, features = ["vendored"], optional = true }

因为
acme-lib
依赖于openssl库,在编译方面可能会相对比较麻烦,需要额外的依赖,此处openssl配置是覆盖acme-lib中的默认features,达到可以不依赖外部openssl库的能力,使用源码编译,所以如果要启用acme-lib能力可以使用

cargo build --features "acme-lib"
如果openssl不好依赖可以使用来编译系统
cargo build --features "acme-lib openssl"

总结

现在免费证书只能申请三个月(之前还能申请十二个月),拥有acme能力对于小的站点来说就比较需要,可以比较好的部署也不用关心TLS带来的烦恼。

点击
[关注]

[在看]

[点赞]
是对作者最大的支持

在硬件性能不断提升的现在,软件性能依旧是开发人员关注的重点。不同类型的程序关注的具体性能指标有所不同,服务器程序注重吞吐量,游戏引擎追求渲染效率,桌面程序则关注内存消耗以及界面加载效率和流畅性。当我们需要进行性能优化时,首先需要找到性能瓶颈。本文将介绍两个WPF性能优化分析工具:
内存使用率

应用程序时间线
的使用。

内存使用率

内存使用率是Visual Studio中集成的诊断工具之一,适用于.Net程序查找内存泄漏或者低效内存适用情况。

默认情况下,调试程序时诊断工具窗口会自动开启停靠在右侧或者底部。如果因为个人适用习惯关闭了诊断工具窗口,可以通过顶部菜单栏找到“调试”—>“窗口”—>“显示诊断窗口”或者快捷键
Ctrl+Alt+F2
打开诊断工具窗口。

诊断工具窗口可以查看程序运行过程CPU和内存消耗的变化,鼠标悬浮在进程内存消耗图上时,会显示任意时间点的内存消耗。

要查看内存使用情况时,可以在诊断工具窗口的内存使用情况选项卡点击“截取快照”按钮。通常我们会在内存显著增加前后各截取一次内存快照,然后对比两次快照中对象和堆大小的差异。
image

上图中显示了两次截图快照的时间、对象个数和堆中的字节数。其中第二条快照信息中对象个数和堆大小中括号内的数值是相对于第一条快照中的变化。对象个数和堆大小这两列中的数值是以超链接形式显示,点击后可以打开选定快照的堆视图。显示了快照捕获的完整的对象集,包括了各类型对象的个数,对象实例大小和非独占大小。点击表头可以对选定列进行排序。
image

并且可以通过堆视图左上角类型筛选器快速查找指定类型的内存信息。下图中显示内存中增加了1800个Student对象实例,占用大约158KB内存。
image

应用程序时间线

应用程序时间线工具集成在Visual Studio中的性能探测器中,用于查找XAML应用程序交互相关的性能问题。该工具提供了详细的视图显示XAML应用程序(目前不支持Avalonia)资源使用情况,可以查看UI线程使用率,可视化吞吐量,UI元素解析、布局及呈现、网络及磁盘I/O所耗费的时间。

使用应用程序时间线工具时,只需单击“调试”—>“性能探测器”或者使用快捷键
Alt+F2
,在“XXX.diagsession 窗口”中看到分析工具。勾选应用程序时间线后点击“开始”按钮进行性能数据收集。需要停止分析时,点击分析窗口左上角的“停止收集”按钮,等待一会儿就会生成详细的视图。在诊断会话窗口的分析工具列表中有个“内存使用率”,勾选后也可以分析内存使用情况(上一小节已详细介绍)。
image

UI线程使用率

UI线程使用率以柱状图的形式呈现每个时间点UI线程使用情况,并用不同色块区分ui元素解析、布局、呈现、I/O、应用程序代码、Xaml其他使用UI线程的占比。UI线程使用率过高的时间点可能表示应用程序响应能力较差,是性能优化需要关注的地方。
image

可视吞吐量(FPS)

“可视吞吐量(FPS)” 折线图显示了应用程序的 UI线程和复合线程上的每秒帧数 (FPS),较低的帧速率也意味着应用程序响应能力较差。
image

时间线详细信息

时间线详细信息视图呈现了每个时间点占用CPU的UI框架子系统和系统组件以及它们占用时间。
主要包括以下几类:

  • 解析:分析XAML文件并创建对象或者元素所消耗的时间。
  • 布局:计算所有需要布局的元素的大小和位置耗用的时间(即在
    Arrange

    Measure

    ApplyTemplate

    ArrangeOverride

    MeasureOverride
    中所用的时间)。在大型应用程序中,可能会同时在屏幕上显示数千个元素。此显示可能会导致UI帧速率降低以及应用程序响应能力相应地变差。
  • 呈现:在屏幕上绘制XAML元素所耗用的时间。
  • I/O:从本地磁盘或从通过Microsoft Windows Internet (WinINet) API访问的网络资源中检索数据所耗用的时间。
  • 应用程序代码:执行与分析或布局无关的应用程序(用户)代码所耗用的时间。
  • Xaml其他:执行 XAML 运行时代码所耗用的时间。

时间线详细信息视图分为左中右三列。左侧显示事件名称,绝大部分事件是发生在UI线程上,这些事件名称前有一个紫色线条标记,非UI线程上的事件则无标记。中间一列顶部显示时间轴,下边显示每个事件的色块标记(与UI线程使用率中色块颜色一致)、持续总计时间(自身和子元素持续时间的总和)和自身持续时间,鼠标悬浮在元素上会显示自身持续时间和事件开始时间。右侧一列则显示选中事件的详细信息描述。
image

上边示例中,UI线程使用率过高,耗时最长的是布局,开始于6.91秒,总耗时6.56秒,涉及33237个元素,其原因就是TreeView没有开启虚拟化,一次性把所有的数据都渲染出来,导致UI响应差。通过这个分析找到性能瓶颈,就可以有的放矢进行优化。这里只需开启虚拟化即可,现实开发中导致性能瓶颈的原因多种多样,需结合实际情况优化解决。

小结

内存使用率

应用程序时间线
是WPF开发过程中不可或缺的两个有效工具,此外,Snoop以及Visual Studio中的实时可视化树、实时属性资源管理器、XAML实时预览、XAML绑定失败、辅助功能检查等工具也能提高开发调试效率。

参考

https://devblogs.microsoft.com/visualstudio/analyze-cpu-memory-while-debugging/
https://learn.microsoft.com/zh-cn/visualstudio/profiling/application-timeline?view=vs-2022