当前位置: 首页 > article >正文

11-C#.Net-多线程-Async-Await篇-学习笔记

一、async/await 基础1.1 什么是async/await定义C# 5.0 (.NET 4.5) 引入的语法糖C# 7.1 开始Main入口也可以使用C# 8.0 支持异步流(await foreach)和异步释放(await using)什么是语法糖由编译器提供的便捷功能底层实现不变但写代码更简洁类似的语法糖var、表达式树、属性的get/set、字符串插值$1.2 基本用法规则// 1. async单独使用会警告没有实际作用 public async void Method1() // 警告 { Console.WriteLine(没有await); } // 2. await必须在async方法内使用 public void Method2() { await Task.Delay(1000); // 编译错误 } // 3. async await 配套使用 public async Task Method3() { await Task.Delay(1000); // 正确 }返回值规则// 无返回值返回Task(推荐)或void public async Task NoReturnAsync() { await Task.Delay(1000); // 默认返回Task } public async void NoReturnVoid() { await Task.Delay(1000); // 不推荐无法await或Wait } // 有返回值返回TaskT public async Taskint GetValueAsync() { await Task.Delay(1000); return 123; // 实际返回Taskint }为什么推荐Task而不是voidTask可以使用await、Wait、WhenAny、WhenAll等方法组合void无法组合使用Task可以捕获异常void不行二、async/await的核心价值2.1 解决的问题问题既要有顺序又要不阻塞// 同步方法有顺序但阻塞 public void SyncMethod() { Console.WriteLine(步骤1); Thread.Sleep(1000); // 阻塞 Console.WriteLine(步骤2); Thread.Sleep(1000); // 阻塞 Console.WriteLine(步骤3); } // 多线程不阻塞但无顺序 public void TaskMethod() { Task.Run(() { Console.WriteLine(步骤1); Thread.Sleep(1000); }); Task.Run(() { Console.WriteLine(步骤2); Thread.Sleep(1000); }); Task.Run(() { Console.WriteLine(步骤3); Thread.Sleep(1000); }); // 输出顺序不确定 } // async/await有顺序不阻塞 public async Task AsyncMethod() { await Task.Run(() { Console.WriteLine(步骤1); Thread.Sleep(1000); }); await Task.Run(() { Console.WriteLine(步骤2); Thread.Sleep(1000); }); await Task.Run(() { Console.WriteLine(步骤3); Thread.Sleep(1000); }); // 按顺序输出步骤1 - 步骤2 - 步骤3 }2.2 核心理念以同步编程的方式来写异步代码代码看起来像同步实际是异步执行降低编程难度保持代码可读性三、await的执行机制3.1 执行流程public async Task TestAsync() { Console.WriteLine($主线程ID: {Thread.CurrentThread.ManagedThreadId}); // 1. 遇到await主线程立即返回(不阻塞) await Task.Run(() { Console.WriteLine($子线程ID: {Thread.CurrentThread.ManagedThreadId}); Thread.Sleep(3000); Console.WriteLine(子线程完成); }); // 2. await后的代码由线程池的线程执行 Console.WriteLine($await后线程ID: {Thread.CurrentThread.ManagedThreadId}); Console.WriteLine(继续执行); }关键点主线程遇到 await 就返回不等待(非阻塞)await 后的代码会在 Task 完成后继续执行await 后的代码可能由子线程、主线程或其他线程执行一个典型的执行顺序示例能直观说明 await 的非阻塞特性public static void Show() { Console.WriteLine($start 线程ID: {Thread.CurrentThread.ManagedThreadId}); // ① 主线程 Async(); // 调用异步方法 Console.WriteLine($aaa 线程ID: {Thread.CurrentThread.ManagedThreadId}); // ③ 主线程继续不等待 } public static async void Async() { Console.WriteLine($ddd 线程ID: {Thread.CurrentThread.ManagedThreadId}); // ② 还是主线程 await Task.Run(() { Thread.Sleep(500); Console.WriteLine($bbb 线程ID: {Thread.CurrentThread.ManagedThreadId}); // ④ 子线程 }); Console.WriteLine($ccc 线程ID: {Thread.CurrentThread.ManagedThreadId}); // ⑤ await后线程池线程 }实际输出顺序start → ddd → aaa → bbb → ccc⚠️ 注意aaa在bbb之前输出——主线程遇到 await 后立即返回继续执行子线程的工作和主线程是并发的。3.2 与ContinueWith的对比async/await 出现之前控制多个异步操作的顺序只能靠ContinueWith链式回调代码可读性很差// 老写法ContinueWith 链式调用控制顺序嵌套深、难维护 Taskint task taskFactory.StartNewint(() { Thread.Sleep(3000); return 123; }).ContinueWith(c { Thread.Sleep(3000); return 234; }).ContinueWith(t { Thread.Sleep(3000); return 345; });用 await 改写后代码结构和同步方法一样直观// 新写法await 顺序执行清晰易读 public async Task UseAwait() { await Task.Run(() { Thread.Sleep(3000); Console.WriteLine(步骤1); }); await Task.Run(() { Thread.Sleep(3000); Console.WriteLine(步骤2); }); await Task.Run(() { Thread.Sleep(3000); Console.WriteLine(步骤3); }); }await 的优势代码更简洁可读性更好写法像同步代码实际是异步执行自动处理异常传播不需要手动在每个 ContinueWith 里 try-catch四、带返回值的async方法4.1 基本用法public async Tasklong SumAsync() { Console.WriteLine($开始计算线程ID: {Thread.CurrentThread.ManagedThreadId}); long result 0; // 第一个计算 await Task.Run(() { Console.WriteLine($计算1线程ID: {Thread.CurrentThread.ManagedThreadId}); for (long i 0; i 999_999_999; i) { result i; } }); Console.WriteLine($计算1完成线程ID: {Thread.CurrentThread.ManagedThreadId}); // 第二个计算 await Task.Run(() { Console.WriteLine($计算2线程ID: {Thread.CurrentThread.ManagedThreadId}); for (long i 0; i 999_999_999; i) { result i; } }); Console.WriteLine($计算2完成线程ID: {Thread.CurrentThread.ManagedThreadId}); return result; // 自动包装成Tasklong }4.2 调用方式// 方式1await(推荐不阻塞) public async Task CallAsync() { long result await SumAsync(); Console.WriteLine($结果: {result}); } // 方式2Result(不推荐阻塞) public void CallSync() { long result SumAsync().Result; // 阻塞相当于同步 Console.WriteLine($结果: {result}); } // 方式3Wait(不推荐阻塞) public void CallWait() { Tasklong task SumAsync(); task.Wait(); // 阻塞 long result task.Result; Console.WriteLine($结果: {result}); }注意访问Result或Wait会阻塞失去async/await的意义推荐使用await保持异步特性五、async/await的底层实现5.1 状态机IL代码分析// C#代码 public async Task SimpleMethod() { await Task.Delay(1000); Console.WriteLine(完成); } // 编译后生成状态机(简化版) private struct StateMachine { public int state; public AsyncTaskMethodBuilder builder; public void MoveNext() { try { switch (state) { case 0: // 执行await之前的代码 var awaiter Task.Delay(1000).GetAwaiter(); if (!awaiter.IsCompleted) { state 1; // 等待完成 return; } goto case 1; case 1: // 执行await之后的代码 Console.WriteLine(完成); state -2; break; } } catch (Exception ex) { state -2; builder.SetException(ex); } builder.SetResult(); } }状态机执行流程实例化状态机将状态机交给builder执行整理线程上下文调用MoveNext()方法根据状态执行不同分支异常时状态重置为-2完成时调用SetResult()5.2 状态机的价值类比红绿灯红灯停止绿灯行驶黄灯减速状态机一个对象在不同状态下执行不同行为await将方法分割成多个状态每个状态对应一个await六、性能对比6.1 文件读取对比// 1. Async版本(推荐) private async Taskbyte[] ReadAsync(string path) { Console.WriteLine($ReadAsync开始线程ID: {Thread.CurrentThread.ManagedThreadId}); var result await File.ReadAllBytesAsync(path); // 读取文件时没有开启新线程 // 主线程告诉系统要做什么然后就返回了 // 降低了线程开启数量降低了CPU负荷 Console.WriteLine($ReadAsync结束线程ID: {Thread.CurrentThread.ManagedThreadId}); return result; } // 2. Task版本 private Taskbyte[] ReadTask(string path) { Console.WriteLine($ReadTask开始线程ID: {Thread.CurrentThread.ManagedThreadId}); var result Task.Run(() { Console.WriteLine($ReadTask执行线程ID: {Thread.CurrentThread.ManagedThreadId}); return File.ReadAllBytes(path); }); // 铁定开启新线程 Console.WriteLine($ReadTask结束线程ID: {Thread.CurrentThread.ManagedThreadId}); return result; } // 3. Sync版本 private byte[] ReadSync(string path) { Console.WriteLine($ReadSync开始线程ID: {Thread.CurrentThread.ManagedThreadId}); var result File.ReadAllBytes(path); Console.WriteLine($ReadSync结束线程ID: {Thread.CurrentThread.ManagedThreadId}); return result; }性能测试结果20次读取大文件 - Async: 约3000ms - Task: 约3500ms - Sync: 约6000ms6.2 Web请求对比// 1. Async版本(推荐) private async Taskstring WebAsync(string url) { HttpWebRequest request HttpWebRequest.Create(url) as HttpWebRequest; // 使用异步版本的API using (HttpWebResponse response await request.GetResponseAsync() as HttpWebResponse) { StreamReader sr new StreamReader(response.GetResponseStream()); return await sr.ReadToEndAsync(); // 异步读取 } } // 2. Task版本 private Taskstring WebTask(string url) { return Task.Run(() { return InvokeWebRequest(url); // 同步方法包装 }); } // 3. Sync版本 private string WebSync(string url) { return InvokeWebRequest(url); }性能测试结果10次Web请求(每次5秒) - Async: 约5秒(并发) - Task: 约5秒(并发) - Sync: 约50秒(串行)6.3 CPU密集型计算对比// 1. Async版本 private async Tasklong CalculationAsync(long total) { return await Task.Run(() { long result 0; for (long i 0; i total; i) { result i; } return result; }); } // 2. Task版本 private Tasklong CalculationTask(long total) { return Task.Run(() { long result 0; for (long i 0; i total; i) { result i; } return result; }); } // 3. Sync版本 private long CalculationSync(long total) { long result 0; for (long i 0; i total; i) { result i; } return result; }性能测试结果10次计算(每次10亿次循环) - Task: 约8秒 - Async: 约8秒 - Sync: 约80秒结论CPU密集型Async和Task性能相同都需要开启线程IO密集型Async性能更好不需要额外线程七、适用场景7.1 适合使用async/await的场景1. IO密集型操作(推荐)// 文件操作 await File.ReadAllBytesAsync(path); await File.WriteAllTextAsync(path, content); // 数据库操作 await connection.OpenAsync(); await command.ExecuteNonQueryAsync(); // Web请求 await httpClient.GetStringAsync(url); // Redis操作 await redis.StringGetAsync(key);2. 与第三方交互(非托管资源)数据库查询Redis缓存Web API调用文件读写7.2 不适合使用async/await的场景1. CPU密集型计算(不推荐)// 不推荐async/await对CPU密集型无优势 public async Tasklong CalculateAsync() { return await Task.Run(() { // 大量计算 long result 0; for (long i 0; i 1_000_000_000; i) { result i; } return result; }); } // 推荐直接使用Task public Tasklong CalculateTask() { return Task.Run(() { long result 0; for (long i 0; i 1_000_000_000; i) { result i; } return result; }); }2. 本地计算(托管资源)内存中的数据处理算法计算数据转换八、不同框架中的应用8.1 ASP.NET Core(推荐使用)[HttpGet] public async TaskIActionResult GetDataAsync() { var data await _service.GetDataAsync(); return Ok(data); }优势不阻塞线程池线程提高服务器并发能力可以处理更多请求8.2 控制台应用(可以使用)// C# 7.1 static async Task Main(string[] args) { await DoWorkAsync(); }8.3 WinForms(需要注意)private async void btnClick_Click(object sender, EventArgs e) { // 不阻塞UI线程 var result await GetDataAsync(); // await后的代码在UI线程执行可以直接更新UI lblResult.Text result; }注意事项事件处理器可以使用async voidawait后的代码会回到UI线程可以直接更新UI控件8.4 WPF(类似WinForms)private async void Button_Click(object sender, RoutedEventArgs e) { var result await GetDataAsync(); TextBlock.Text result; }九、C# 8.0 新特性9.1 异步流(await foreach)// 生成异步序列 public async IAsyncEnumerableint GenerateSequence() { for (int i 0; i 20; i) { await Task.Delay(100); yield return i; } } // 消费异步序列 private async Task ConsumeAsync() { await foreach (var i in GenerateSequence()) { Console.WriteLine($接收到: {i}); } }9.2 异步释放(await using)await using (var resource new AsyncDisposableResource()) { // 使用资源 } // 自动调用DisposeAsync()十、最佳实践10.1 命名规范// 异步方法以Async结尾 public async Taskstring GetDataAsync() { return await _repository.QueryAsync(); }10.2 避免async void// 错误无法捕获异常无法await public async void BadMethodAsync() { await Task.Delay(1000); } // 正确返回Task public async Task GoodMethodAsync() { await Task.Delay(1000); } // 例外事件处理器可以使用async void private async void Button_Click(object sender, EventArgs e) { await DoWorkAsync(); }10.3 避免阻塞// 错误阻塞 var result GetDataAsync().Result; // 错误阻塞 GetDataAsync().Wait(); // 正确异步等待 var result await GetDataAsync();10.4 ConfigureAwait// 库代码中使用避免捕获上下文 var result await GetDataAsync().ConfigureAwait(false); // UI代码中不使用需要回到UI线程 var result await GetDataAsync();十一、小结async/await 是语法糖底层是状态机实现核心价值以同步方式写异步降低编程难度适用场景IO 密集型操作(文件、网络、数据库)不适用场景CPU 密集型计算性能提升提高吞吐量不是降低单个请求时间避免阻塞不要使用 Result 或 Wait命名规范异步方法以 Async 结尾返回类型优先使用 Task避免 void

相关文章:

11-C#.Net-多线程-Async-Await篇-学习笔记

一、async/await 基础 1.1 什么是async/await 定义 C# 5.0 (.NET 4.5) 引入的语法糖C# 7.1 开始,Main入口也可以使用C# 8.0 支持异步流(await foreach)和异步释放(await using) 什么是语法糖 由编译器提供的便捷功能底层实现不变,但写代码更简洁类似的语…...

Fish Speech 1.5声音克隆教程:如何用手机录音制作高质量参考音频

Fish Speech 1.5声音克隆教程:如何用手机录音制作高质量参考音频 想用自己的声音,或者朋友、家人的声音,来生成一段全新的语音吗?Fish Speech 1.5的声音克隆功能就能帮你实现。但很多人第一步就卡住了:怎么录一段合格…...

腾视科技AI大模型应用:提效、破局与落地,重塑智能新生态

当AI大模型技术从实验室走向产业落地,企业却普遍面临 “成效难显、成本高企、复用性差” 的三重困境。腾视科技深耕大模型应用领域,以 “顶层设计 敏捷迭代” 的方法论,结合全栈式技术产品矩阵,推出AI大模型应用解决方案&#xf…...

实测coze-loop:粘贴代码选目标,AI自动重构+解释优化思路

实测coze-loop:粘贴代码选目标,AI自动重构解释优化思路 1. 为什么开发者需要智能代码优化工具 在日常开发中,我们经常遇到这样的困境:一段功能正常的代码,随着业务发展逐渐暴露出性能瓶颈或可维护性问题。传统优化方…...

腾视科技重磅推出TensorAI智能体平台,开启智能助手新体验

在人工智能技术飞速发展的当下,浙江腾视算擎科技有限公司(以下简称:腾视科技TENSORTEC)凭借深厚的技术积累与创新思维,正式推出腾视科技TensorAI类“豆包”应用平台(AI智能体平台),为…...

90后农学毕业,放弃高薪销售,逆袭转型人工智能,我经历了什么?!转行人工智能大模型

我叫王东,90后,和大家分享一下我的人工智能转型之路。 农学毕业,投身互联网做销售 机遇难求,养殖梦碎 我是土生土长的农村人,小时候经常和小鱼小虾打交道,上大学的时候就选择了农学专业,想着毕业…...

DeerFlow应用案例:如何用AI研究助手快速分析行业趋势并生成报告

DeerFlow应用案例:如何用AI研究助手快速分析行业趋势并生成报告 1. 引言:当研究遇上AI,效率革命正在发生 想象一下这个场景:老板在周一晨会上突然问你:“小张,下周我们要开一个关于‘AI智能体在金融风控领…...

SQL如何多字段取极值?| 附多行业案例实战

目录 一、先理清:多字段取极值的两类核心场景 二、GREATEST()/LEAST()基础用法 1. 函数语法 2. 基础示例 三、最易踩的坑:NULL值的致命影响 1. 坑的示例 四、NULL值坑的解决方案:替换空值再取极值 1. 通用方案:COALESCE函数(所有数据库兼容) 修复后的示例代码 …...

叠加百分比标签

Matlab&python绘制混淆矩阵求解绘图,带百分比的混淆矩阵,颜色多变风格多样。最近在模型评估的时候发现,带百分比的混淆矩阵比纯数字版本直观太多了。今天直接上代码,聊聊Matlab和Python两种实现方案,顺便分享几个配…...

119,376个英语单词发音MP3:构建你的专属英语发音库

119,376个英语单词发音MP3:构建你的专属英语发音库 【免费下载链接】English-words-pronunciation-mp3-audio-download Download the pronunciation mp3 audio for 119,376 unique English words/terms 项目地址: https://gitcode.com/gh_mirrors/en/English-word…...

学术引用效率提升指南:Zotero与GB/T 7714-2015开源配置全攻略

学术引用效率提升指南:Zotero与GB/T 7714-2015开源配置全攻略 【免费下载链接】Chinese-STD-GB-T-7714-related-csl GB/T 7714相关的csl以及Zotero使用技巧及教程。 项目地址: https://gitcode.com/gh_mirrors/chi/Chinese-STD-GB-T-7714-related-csl 在学术…...

AI检测率太高论文过不了?这4个降AI率网站2026年必须用!

降AI率工具已成为学术写作中不可或缺的辅助手段。随着AIGC检测技术的不断升级,越来越多高校和期刊开始采用权威平台如知网、Turnitin等进行查重与AI痕迹检测。结合多所高校师生的实际使用反馈及最新检测报告,本文将深入解析当前最有效、最值得信赖的降AI…...

老Mac升级指南:借助OpenCore Legacy Patcher实现macOS支持延长

老Mac升级指南:借助OpenCore Legacy Patcher实现macOS支持延长 【免费下载链接】OpenCore-Legacy-Patcher 体验与之前一样的macOS 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 随着苹果对旧款硬件的支持周期不断缩短&#xf…...

EVE-NG汉化后F5不生效?聊聊Web界面缓存机制与正确刷新方式

EVE-NG汉化后F5不生效?聊聊Web界面缓存机制与正确刷新方式 你是否遇到过这样的情况:按照教程一步步完成了EVE-NG的汉化操作,满怀期待地刷新页面,却发现界面依然顽固地显示着英文?这并非汉化失败,而是浏览器…...

全网最全 9个降AI率平台测评:全学科适配,2026最新推荐

在学术写作日益依赖AI工具的当下,如何有效降低AIGC率、去除AI痕迹并保持论文的原创性与流畅性,成为众多学者和学生的共同难题。AI降重工具应运而生,不仅能够精准识别AI生成内容的特征,还能在不改变原意的前提下进行语义优化&#…...

算法篇:二分查找

目录 介绍 查找数组中值算法模板 左右边界模板 实例 二分查找(easy) 在排序数组中查找元素的第一个和最后一个(medium) 搜索插入位置(easy) x 的平方根(easy) 山峰数组的峰…...

保姆级教程:用Go的net/smtp库绕过第三方email包,直连QQ邮箱465端口发邮件

深度解析:如何用Go标准库直连QQ邮箱465端口实现稳定邮件发送 在开发邮件发送功能时,许多Golang开发者会首选第三方封装库如jordan-wright/email,它们提供了简洁的API和便捷的抽象。然而在实际生产环境中,这些封装库可能会遇到一些…...

新手必看!数学建模国赛‘穿越沙漠‘题保姆级通关攻略

数学建模国赛"穿越沙漠"题全维度实战指南 1. 理解题目本质与核心挑战 "穿越沙漠"作为数学建模国赛经典题型,本质上是一个多约束条件下的资源优化问题。我们需要在负重限制、天气变化、资金管理等复杂条件下,找到从起点到终点的最优路…...

基于Lasso分位数回归的多变量时间序列预测 Lasso多变量时间序列 matlab代码, 注

基于Lasso分位数回归的多变量时间序列预测 Lasso多变量时间序列 matlab代码,注:暂无Matlab版本要求 -- 推荐 2018B 版本及以上咱们今天聊聊怎么用Matlab玩转Lasso分位数回归预测多变量时间序列。这事儿听着挺学术,但实际操作起来比想象中有趣…...

如何高效解决网页资源获取难题?猫抓媒体解析工具的技术突破与实用价值

如何高效解决网页资源获取难题?猫抓媒体解析工具的技术突破与实用价值 【免费下载链接】cat-catch 猫抓 chrome资源嗅探扩展 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 在信息爆炸的今天,网页媒体资源已成为学习、工作和娱乐的…...

基于Python的物资综合管理系统毕业设计源码

博主介绍:✌ 专注于Java,python,✌关注✌私信我✌具体的问题,我会尽力帮助你。一、研究目的本研究旨在开发一套基于Python的物资综合管理系统,以实现对物资采购、存储、分配和回收等环节的全面管理。具体研究目的如下:提高物资管理…...

160+功能重构OneNote体验:OneMore插件让笔记效率提升300%的实战指南

160功能重构OneNote体验:OneMore插件让笔记效率提升300%的实战指南 【免费下载链接】OneMore A OneNote add-in with simple, yet powerful and useful features 项目地址: https://gitcode.com/gh_mirrors/on/OneMore 作为全球最受欢迎的数字笔记工具之一&a…...

OpenClaw压力测试:Qwen3.5-9B持续工作72小时稳定性报告

OpenClaw压力测试:Qwen3.5-9B持续工作72小时稳定性报告 1. 测试背景与目标 去年夏天,当我第一次在个人笔记本上部署OpenClaw时,最担心的不是功能实现,而是这个"数字员工"能否稳定工作。作为需要7*24小时运行的自动化框…...

深入浅出 LINQ:从传统集合操作到语言集成查询的进化

在 C# 开发中&#xff0c;我们经常需要对内存中的集合&#xff08;如数组、List<T>、Dictionary<TKey, TValue>&#xff09;进行筛选、排序、分组等操作。过去&#xff0c;我们通常使用 foreach 循环、for 循环&#xff0c;或借助委托来实现这些功能。然而&#xf…...

LispMotor:Arduino L298N双H桥电机驱动轻量库

1. 项目概述LispMotor 是一款专为 Arduino 平台设计的 L298x 系列双 H 桥电机驱动芯片的轻量级控制库。其核心目标并非提供抽象层或高级运动规划&#xff0c;而是以嵌入式工程师的务实视角&#xff0c;直击硬件控制本质&#xff1a;精准映射引脚功能、明确 PWM 使能逻辑、暴露底…...

SDRPlusPlus铁路GSM-R信号解析实践指南:从信号捕获到协议分析

SDRPlusPlus铁路GSM-R信号解析实践指南&#xff1a;从信号捕获到协议分析 【免费下载链接】SDRPlusPlus Cross-Platform SDR Software 项目地址: https://gitcode.com/GitHub_Trending/sd/SDRPlusPlus 在现代铁路通信系统中&#xff0c;GSM-R&#xff08;Global System …...

3分钟掌握「阅读」APP书源导入:告别小说断更,实现阅读自由!

3分钟掌握「阅读」APP书源导入&#xff1a;告别小说断更&#xff0c;实现阅读自由&#xff01; 【免费下载链接】Yuedu &#x1f4da;「阅读」APP 精品书源&#xff08;网络小说&#xff09; 项目地址: https://gitcode.com/gh_mirrors/yu/Yuedu 你是否遇到过这样的情况…...

一个让人上头的数字小游戏:2048到底好玩在哪?

如果你平时喜欢轻量、随开随玩的小游戏&#xff0c;那你大概率已经听说过“2048”。这类游戏没有复杂操作&#xff0c;却非常容易让人一玩就是几十分钟&#xff0c;甚至停不下来。 最近我在体验一个在线版本的时候&#xff0c;重新梳理了一下这个游戏的核心玩法和设计逻辑&…...

如何解决B站m4s格式播放限制:m4s-converter工具全面指南

如何解决B站m4s格式播放限制&#xff1a;m4s-converter工具全面指南 【免费下载链接】m4s-converter 将bilibili缓存的m4s转成mp4(读PC端缓存目录) 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter B站缓存的视频文件采用特殊的m4s格式存储&#xff0c;导致…...

告别多设备切换烦恼:跨设备协作效率工具Lan Mouse全解析

告别多设备切换烦恼&#xff1a;跨设备协作效率工具Lan Mouse全解析 【免费下载链接】lan-mouse mouse & keyboard sharing via LAN 项目地址: https://gitcode.com/gh_mirrors/la/lan-mouse 在数字化办公环境中&#xff0c;跨平台键鼠共享已成为提升工作效率的关键…...