ProjectO
索引代码热更新资源加载ET框架8.1物理模拟动画技能编辑器数据库MongoDB渲染
OW中的英雄角色和运载目标之间是存在碰撞的, 运载目标和生命之树之间 如果生命之树生成在地上运载目标不会和树发生碰撞 生成在车上就会有碰撞 角色和生命之树有碰撞
The performance characteristics of async methods in C#
在最近的两篇博客文章中,我们深入探讨了C#异步方法的内部实现机制,并详细分析了C#编译器提供的扩展点如何调整异步方法的行为。今天,我们将重点研究异步方法的性能特征。
正如本系列第一篇文章所述,编译器进行了大量转换工作,使异步编程体验几乎与同步编程无异。但为了实现这一点,编译器需要创建状态机实例、将其传递给异步方法生成器、调用任务等待器等。显然,所有这些逻辑都会带来性能开销,但具体代价有多大呢?
在TPL(任务并行库)出现之前,异步操作通常粒度较粗,因此其开销往往可以忽略不计。但在现代应用中,即使相对简单的程序每秒也可能执行成百上千次异步操作。TPL虽然针对这种工作负载进行了优化设计,但它并非魔法,仍然存在一定开销。
为了准确测量异步方法的开销,我们将对首篇博客中的示例进行适当调整后作为测试基准。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162public class StockPrices{ ...
C#中的Task
关于TaskSchedulerTaskScheduler是C#System.Threading.Tasks命名空间下的一个抽象类, 在.Net Framework 4.8中C#内置了三个TaskScheduler的派生类: ConcurrentExclusiveScheduler、SynchronizationContextTaskScheduler、ThreadPoolTaskScheduler.
其中的ThreadPoolTaskScheduler, 被设置为了Default TaskScheduler
ExecutionContext执行上下文,
StackCrawlMark关于Thread调用Task.Run的时候 发生了什么
Extending the asynce methods in CSharp(译)
首先要搞清楚, 为什么要实现我们自己的异步, 我们可以使用Task来完成我们的异步操作, 我们将需要异步的操作用Task包装, Task(Awaiter)何时结束、如何调度都是TaskScheduler做的, 我们没有太多权限干预 我们能做的只是告诉Task 等你的任务完成之后 你需要调用stateMachiner的MoveNext方法, 如果我要实现自己的类似ETTask的功能, 我希望我能自己决定如何调度这些ETTask, 要不然实现自己的ETTask的意义就没有了. C#中异步操作到这里就已经揭示地比较清楚了, 只是Task的调度目前对我们来说还是黑盒, 最好它也只是黑盒
因此, 在笔者看来, 要实现自己的异步, 就是要实现如何调度这些异步操作, 即我们要实现TaskScheduler的功能.
我们使用Task包装一些操作, 这些操作可能是ComputeBound类型或者IOBound类型, 也可能只是一个延时Task.Delay操作, 甚至可能就是一个简单的同步方法. 不论是什么操作, 你只要将该操作使用Task包装, 并且调用了该Task, 那么你就可以通过获取Task.Ge ...
Dissecting the async methods in CSharp
关于异步的历史C#开发者在第一次接触异步的概念, 应该是通过Task类型. Task是在.Net 4.0的时候被引入的. 一个task就是一个work单元, 且该task承诺, 在这个task未来完成的时候, 会将结果返回给task的调用者. 这个Task可能是由IO操作支持或者计算密集型操作(这正好对应了笔者在协程部分所说的CPU密集型操作和IO型操作, Unity协程只能用来做IO型的异步操作). 重要的是该操作的结果是自包含的,且具有一等公民身份。你可以自由传递这个”未来”:将其存储在变量中、从方法返回它、或传递给其他方法。你可以将两个”未来”合并形成新的任务,可以同步等待结果,也可以通过添加”延续”来”等待”结果。仅凭任务实例本身,你就能决定在操作成功、失败或被取消时采取何种处理。
任务并行库 (TPL) 改变了我们对并发编程的认知,而 C# 5 通过引入 async/await 进一步推动了这一发展。async/await 让任务的组合变得更加容易,并允许开发者使用熟悉的代码结构,如 try/catch、using 等。但是async/await也有其开销. 要理解具体开销是 ...
精细化实验策略下一种多策略交叉的美术、动效框架的实现
笔者对精细化实验的定义当游戏要新增或改动一个功能时, 如果开发者不能确其会对用户体验造成什么影响, 就会用做实验的方式将功能发布到线上, 即将用户分为对照组和实验组, 看两组用户的数据表现, 来判断该功能的好坏. 如果实验组数据好于对照组, 则应用实验组, 反之应用对照组. 不论应用哪一组, 没有被应用的那一组的硬编码就可以删除掉, 因此实验组与对照组逻辑的代码只是会临时插入到项目中, 只要实验应用了之后把非应用实验分支的代码删干净, 长期下来是不会对整个项目代码结构有什么影响. 但是我们团队在按照上述模式推进了一段时间之后发现了这种模式存在问题, 同一个版本上线的多个实验之间存在交叉, 且会对单个实验的结果产生影响, 而且有的实验短期内是实验组的数据好, 而长期又变成了对照组的数据好. 因此, 我们认为之前对实验数据的结论不准确, 我们决定将一段周期内最早的那个版本的项目状态定义为基线组, 在这一周期内的每个版本会上线的实验都不会应用, 而是会一直在线上跑, 随着实验越开越多, 实验之间的交叉问题越来越严重, 又因为这些实验的分支代码不会删除, 代码也会变得越来越难以维护, 上述这种 ...
时势:周期波动下的国家、社会和个人
日本失落的三十年日本财团
广场协议
国债
对C#中类型转换和拆装箱的思考
类型转换的开销来自哪里?类型转换在时间和空间上都会造成开销, 因为类型转换C#编译器会生成额外的类型转换处理代码, 导致代码文件的体积变大. 既然有额外的IL代码生成则就需要有额外的时间去执行. 如果类型转换中涉及到装拆箱操作, 则还会对运行时内存产生影响.
CLR会生成额外IL代码执行类型转换是否合法的判断逻辑, 这部分开销是不可避免的, 即便开发者笃定类型转换必定合法. 不过这部分开销的影响并不大. 如果类型转换判定为不合法, 则需要额外的开销来处理异常, 我们通常会有两种类型转换的方式, 两种转换方式不合法的处理的开销不同:
使用类型强转, 即var a = (someType)b形式, 这种方式在转换不合法的时候会抛出异常, 开销较大.
使用as操作符,即var a = b as someType, 这种方式在转换不合法的时候会将a字段设置为null,开销小. 更推荐使用这种方式.不过如果你真的笃定类型转换不会出错, 那么以上这两种类型转换的方式其实影响不大.
拆箱装箱造成的开销, 请记住这句话:装箱拆箱一定是类型转换造成的, 但是类型转换不一定会导致装箱拆箱 装拆箱详解 ...
UnityUGUI源码
UGUI的渲染和3D物体渲染的区别?笔者希望各位读者区分两个概念: 渲染和渲染所需的数据更新. Unity首先更新其要渲染物体的渲染数据, 然后根据渲染数据将画面渲染到屏幕上, 笔者在最初接触这部分内容时, 把两者混为一谈, 给自己造成了理解上的困难, 因此在此说明.
UGUI的渲染发生在每一帧CanvasUpdateRegistry的SendWillRenderCanvases()接口被调用之后,不过我们看不到调用该接口的代码,猜测调用处通过[RequireByNativeCode]特性隐藏在了Unity更底层的引擎代码中. 但这不影响我们知道UGUI的渲染位于事件执行的什么位置. 为了知道这几个事件的执行顺序,因为这些事件是在每一帧都执行的, 所以使用断点调试的方式不太方便, 于是笔者尝试使用Log方式观察几个事件的执行顺序,
CanvasScaler按照标准分辨率来设计的
Unity粒子的Render和Canvas的OrderInLayer是一样的 用来处理渲染层级的问题
常规模型和粒子满足谁距离摄像机近谁后渲染在Unity中创建一个场景,创建两个Image,然后创建一个Sp ...
Unity事件更新顺序
Unity事件执行顺序图
Awake、OnDestroy、OnEnable、OnDisable四者执行时机梳理OnEnable方法在AddComponent、跟随预制体实例化(这里要十分注意挂载该脚本的GameObj的avtive状态要是true的情况下才会触发!)、在代码中手动设置enable为true的时候会调用.OnDisable方法是跟随预制体被删除、在代码中手动设置enable为false的时候会调用.
Awake方法在AddComponent、跟随预制体实例化的时候会调用(这里要十分注意挂载该脚本的GameObj的avtive状态要是true的情况下才会触发!). 整个组件的生命周期只会调用一次OnDestroy方法在跟随预制体被删除的时候调用. 整个组件的生命周期只会调用一次 注意,如果在这个预制体被实例化到销毁的这一生命周期内,挂载该脚本的节点的active一直都是false,那么就不会触发Awake,也不会触发OnDestroy,只要该节点被active过,即便被销毁的时候该节点处在not active状态,也会触发OnDestroy
ExecuteAlways P ...