通过CLR #Bytes in all Heaps在过去几天监视全新.NET 4.5服务器应用程序的性能计数器,我可以注意到一种模式让我认为Gen2集合并不总是收集死对象,但我无法理解究竟发生了什么.
服务器应用程序使用Server GC/Background在.NET Framework 4.5.1中运行.
这是一个托管为Windows服务的控制台应用程序(在Topshelf框架的帮助下)
服务器应用程序正在处理消息,并且吞吐量在某种程度上是不变的.
我可以看到的图表CLR #Bytes in all Heaps是内存开始大约为18MB,然后在大约20-24小时内增长到35MB(在此时间范围内有20-30个Gen2集合),然后突然回到名义价值为18MB,然后在20-24小时内再次增长到~35MB,然后又回落到18MB,等等(我可以看到应用程序现在正在运行的过去6天重复的模式)...内存不是线性的,大约需要5个小时才能增长10MB,然后剩下的10 MB大约需要15-17个小时.
事情就是我可以通过查看perfmon计数器看到#Gen0/#Gen1/#Gen2 collections一堆Gen2集合在20-24小时内(可能是30周年)正在进行,并且没有一个使内存回落到标称18MB.然而,奇怪的是通过使用外部工具强制GC(在我的情况下为Perfview),然后我可以看到#Induced GC上升1(GC.Collect被调用,所以这是正常的)并且内存立即回到标称值18MB.
这让我想到#Gen2系列的perfmon计数器不对,只有一个Gen2系列在20-22小时后发生(meeehhh我真的不这么认为)或者Gen2系列并不总是收集死亡对象(看起来似乎更合理)......但在这种情况下,为什么会通过GC.Collect强制GC执行操作,在应用程序的生命周期中明确调用GC.Collect与自动触发的集合之间的区别是什么.
我确信有一个非常好的解释但是从我发现的关于GC的文档的不同来源很少:( - Gen2集合在任何情况下都会收集死对象.所以也许文档不是最新的或者我读错了...欢迎任何解释.谢谢!
编辑:请#Bytes in all heaps在4天内查看该图表的屏幕截图
(点击查看大图)

这比试图描绘你头脑中的东西更容易.您在图表上看到的就是我上面所说的...内存增加超过20-24小时(以及在此时间范围内20-30 Gen2集合),直到达到~35MB然后突然下降.您将在图表的末尾注意到,通过外部工具触发的诱导GC I会立即将内存降回到标称值.
编辑#2:我在代码中做了很多清理,主要是关于终结器.我有很多关于一次性类型的类,所以我必须IDisposable在这些类型上实现.然而,在任何情况下,我都被一些文章误导为使用Finalizer实现Diposable模式.在阅读了一些MSDN文档之后,我开始明白只有当类型本身拥有本机资源时才需要终结器(在这种情况下,使用SafeHandle可以避免这种情况).所以我从所有这些类型中删除了所有终结器.代码中还有一些其他的修改,但主要是业务逻辑,没有".NET框架"相关.现在图表非常不同,这是一个平坦的线路,现在已经持续了20天......正是我期待看到的!所以这个问题现在已经解决了,但是我仍然不知道是什么问题导致......似乎它可能与终结器有关,但仍然无法解释我注意到的内容,即使我们没有调用Dispose (true) - 压缩终结器 - 终结器线程应该在收集之间启动而不是每20-24小时?考虑到我们现在已经摆脱了问题,需要时间回到"越野车"版本并再次重现它.我可能会尝试做一段时间,然后深究它.
编辑:添加了Gen2集合图(点击查看大图)

这不是另一个TcpClient vs Socket.
TcpClient是Socket类的一个包装器,用于简化开发,同时也暴露了底层的Socket.
还是......
在TcpClient类的MSDN库页面上,可以阅读以下注释:
TcpClient类提供了在同步阻塞模式下通过网络连接,发送和接收流数据的简单方法.
而对于Socket类:
Socket类允许您使用ProtocolType枚举中列出的任何通信协议执行同步和异步数据传输.
要仅通过TcpCient异步发送/接收某些数据,必须调用GetStream,通过在TAP模式上调用ReadAsync和WriteAsync方法,从中检索基础NetworkStream,可以异步读/写数据. (可能使用async/await构造).
要通过Socket异步发送/接收一些数据(我不是专家,但我认为我做对了),我们可以通过调用BeginRead/EndRead BeginWrite/EndWrite(或者只是ReadAsync或者)来直接读取/写入套接字实例本身. WriteAsync ..不暴露TAP模式 - 即不返回任务..令人困惑).
首先,任何想法为什么.NET 4.5中的Socket类没有以任何方式实现TAP模式,即ReadAsync和WriteAsync返回Task(如果调用不同的事件以保留向后compat)?
无论如何,很容易从APM模型方法对构建一个Task方法,所以让我说我调用这个异步方法(用于读取)ReadAsyncTAP(返回一个Task).
好 ?所以现在让我说我想编写一个客户端方法async Task<Byte[]> ReadNbBytes(int nbBytes),我将从我的代码调用异步从网络读取一定数量的字节.
完全基于TcpClient的此方法的实现将通过调用GetStream来获取NetworkStream,并且将包含等待ReadAsync调用的异步循环,直到缓冲区满.
基于Socket的这种方法的实现将包含一个等待ReadAsyncTAP的异步循环,直到缓冲区满.
在一天结束时,从客户端代码的角度来看,我认为它没有任何区别.在这两种情况下,呼叫await ReadNbBytes将立即"返回".但是,我认为它在幕后产生了影响......对于TcpClient,依赖于NetworkStream,与直接使用套接字相比,读取是否会以某种方式阻塞或不阻止?如果没有,那么在谈论同步阻塞模式时,TcpClient的注释是错误的吗?
如果有人能澄清,将非常感激!
谢谢.
编辑2:我已经解决了问题(请参阅下面的答案)请注意,该问题可能会影响所有使用BufferingForwardingAppender修饰的appender以及从BufferingAppenderSkeleton继承的所有appender(分别为:AdoNetAppender,RemotingAppender,SmtpAppender和SmtpPickupDirAppender)*
我正在做一些非常基本的log4net工作台,我尝试用BufferingForwardingAppender修饰RollingFileAppender.
我通过BufferingForwardingAppender遇到了糟糕的性能,而不是直接通过RollingFileAppender,我真的没有理由.
这是我的配置:
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="c:\" />
<appendToFile value="false" />
<rollingStyle value="Composite" />
<datePattern value="'.'MMdd-HH'.log'" />
<maxSizeRollBackups value="168" />
<staticLogFileName value="false" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
</layout>
</appender>
<appender name="BufferingForwardingAppender" type="log4net.Appender.BufferingForwardingAppender">
<bufferSize value="512" />
<appender-ref ref="RollingLogFileAppender" />
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="BufferingForwardingAppender" />
</root>
Run Code Online (Sandbox Code Playgroud)
这是基准(非常简单的代码):
var stopWatch = new Stopwatch();
stopWatch.Start();
for (int i = 0; i < 100000; i++)
{
Log.Debug("Hello");
}
stopWatch.Stop();
Console.WriteLine("Done in {0} ms", stopWatch.ElapsedMilliseconds); …Run Code Online (Sandbox Code Playgroud) 假设我有这两个非常基本的实体:
public class ParentEntity
{
public int Id;
public virtual ICollection<ChildEntity> Childrens;
}
public class ChildEntity
{
public int Id;
public int ParentEntityId; // Foreign Key
public virtual ParentEntity parent; // [NOTWANTED]
}
Run Code Online (Sandbox Code Playgroud)
出于某些原因,我不希望ChildEntity将引用保留回其父级.我只是希望它保留ParentEntity id,但仅此而已.到目前为止,没问题,我只是删除[NOTWANTED]行,一切都按预期工作.
我的问题是:如何在特定情况下禁用级联删除?
如果我仍然拥有父导航属性,那将很容易:
modelBuilder.Entity<ChildEntity>()
.HasRequired(c => c.parent)
.WithMany(p => p.Childrens)
.WillCascadeOndelete(false)
Run Code Online (Sandbox Code Playgroud)
但是如果没有导航属性,我不知道如何在删除时禁用级联(当然不是全局禁用它,也不是每个表都禁用它,而只是为了关系).
我现在所做的是将外键设置为可以为空的int,以便在删除时禁用级联,但这并不漂亮:
public int? ParentEntityId; // Foreign Key - nullable just to disable cascade on delete
Run Code Online (Sandbox Code Playgroud)
如何才能使用流畅的API?认为它应该是可能的.
c# entity-framework foreign-keys cascading-deletes ef-code-first
我已经习惯了XUnit.net来完成我的C#项目中的所有单元测试需求.对于我的个人项目,我使用TeamCity作为我的CI服务器,并使用正确的插件,正确启动了单元测试并在TeamCity中正确报告结果.
但是对于工作,我们有限制使用Jenkins作为我们的CI服务器.我想知道Jenkins是否正确支持XUnit.net(以及XUnit.net理论).我见过一个可以支持各种XUnit框架的Jenkins插件XUnit,但是我没有找到任何与Jenkins中的XUnit.net框架支持有关的指南或讨论.
我想知道我是否可以开始实施基于XUnit.net的单元测试(也有理论),并且放心Jenkins将正确报告所有运行的测试(并正确地将每个理论视为一个独特的测试)......或者如果我遇到问题,应该使用NUnit代替.
这是方法重载的一个非常基本的示例,两个方法具有相同的名称但具有不同的签名:
int MyMethod(int a)
int MyMethod(int a, string b)
Run Code Online (Sandbox Code Playgroud)
现在假设我定义了两个通用接口,共享完全相同的名称但具有不同数量的类型参数,例如:
IMyInterface<T>
IMyInterface<T1,T2>
Run Code Online (Sandbox Code Playgroud)
我可以说这代表"通用接口重载"吗?或者"重载"术语是否仅适用于此类背景下的方法?它仍然看起来与方法重载非常相似,因为我们保持一个完全相同的名称,但改变参数.
如果我不能说"通用接口过载/重载"我可以说这两个不同的接口共享相同的名称?
感谢和抱歉,如果这是一个愚蠢的问题,但谷歌搜索"通用接口重载"或"通用接口重载"并没有给我很多,但有关接口方法重载的结果,这不是我感兴趣的.
我试图找到C#服务器应用程序的瓶颈在哪里未充分利用CPU.我认为这可能是由于磁盘I/O性能不佳而与应用程序本身无关,但我无法从这个假设中得出一个事实.
应用程序从本地MSMQ队列中读取消息,对每条消息进行一些处理,并在处理完消息后,将响应消息发送到另一个本地MSMQ队列.
我正在使用异步循环从队列中读取消息,尽可能快地将它们出列并使用Task.Run调度它们进行处理以启动每个消息的处理(并且不要等待此Task.Run ..只是附加一个延续只记错了记录错误).每个消息被同时处理,即在处理下一个消息之前不需要等待消息被完全处理.
在处理消息的最后,我使用MessageQueue的Send方法(不知何故异步但不是真的因为它必须等待磁盘写入才能返回-see System.Messaging - 为什么MessageQueue不提供异步版本的Send).
对于基准测试,我在队列中排队100K消息(100K消息的总大小约为100MB)然后我启动程序.在我的两台个人计算机上(一台SSD HD和另一台SATA2 HD,i7 CPU四核-8逻辑处理器)我在程序生命周期内达到~95%的CPU使用率(将100K消息出列,处理它们和发送回复).消息尽可能快地出列,尽可能快地处理(这里涉及的CPU),然后对发送到不同本地队列的每个消息进行响应.
现在在运行非HT双核CPU的虚拟机上(不知道什么是底层磁盘,但似乎远不如地雷......在基准测试期间,使用Perfmon我可以看到avg disk sec/write arround 10-15 ms on this虚拟机,而在我的个人计算机上大约2ms)当我运行相同的工作台时,我只能达到~55%的CPU(当我在机器上运行相同的工作台而不向队列发送响应消息时我达到~90%CPU ).
我真的不明白这里有什么问题.似乎很清楚,向队列发送消息是问题并且减慢了程序的全局处理(以及要处理的消息的队列化),但为什么会考虑我使用Task.Run来启动每个出列消息的处理并最终响应发送,我不希望CPU未被充分利用.除非当一个线程正在发送消息时它阻止其他线程在等待返回(磁盘写入)时在同一个核心上运行,在这种情况下,考虑到延迟远远高于我的个人计算机,它可能是有意义的,但是一个线程等待I/O不应该阻止其他线程运行.
我真的想了解为什么我没有达到这台机器至少95%的CPU使用率.我盲目地说这是由于较差的磁盘I/O性能,但我仍然不明白为什么它会导致CPU利用不足,因为我正在使用Task.Run同时运行处理.它也可能是一些与磁盘完全无关的系统问题,但考虑到MessageQueue.Send似乎是问题,并且此方法最终将消息写入内存映射文件+磁盘,我看不出性能问题可能来自何处除了磁盘.
当然,由于程序最大限度地提高了我自己计算机上的CPU使用率,因此确实存在系统性能问题,但我需要找到VM系统上的瓶颈,以及为什么它会影响我的应用程序的并发/速度.
任何的想法 ?
我目前正在为二进制应用程序协议开发库.以下代码示例无法编译(抽象类型中不允许使用静态方法定义.太糟糕了:(),但显示了我的意图:
public abstract class CmdBody<T>
{
public static abstract byte[] ToBytes(T cmdBody);
public abstract int GetLength();
}
public class CmdBodyA : CmdBody<CmdBodyA>
{
static override byte[] ToBytes(CmdBodyA cmdBody)
{ // Implementation }
}
public class CmdBodyB : CmdBody<CmdBodyB>
{
static override byte[] ToBytes(CmdBodyB cmdBody)
{ // Implementation }
}
[...]
public class Cmd<T>
{
public CmdHeader Header { get; set; }
public CmdBody<T> Body { get; set; }
public byte[] ToBytes()
{
byte[] cmdBytes = new byte[Header.GetLength() + Body.GetLength()];
Header.ToBytes().CopyTo(cmdBytes, …Run Code Online (Sandbox Code Playgroud) 我知道await不能用在catch子句中.但是我到目前为止还没有遇到过与此相关的问题......
基本上我有一个层负责接收传入的请求,处理它们,从它们构建消息并将消息传递给另一个负责发送消息的层.
如果在发送消息期间出现问题,则会抛出自定义异常,并由消息发送层捕获.此时,应该在DB中插入此消息的失败记录(需要一些时间,因此异步),并且异常应该传播到上层,该层负责向发出请求的客户端发送错误响应.
以下是一些非常简化的代码,用于说明目的:
public async Task ProcessRequestAsync(Request req)
{
int statusCode = 0;
try
{
await SendMessageAsync(new Message(req));
}
catch(MyCustomException ex)
{
statusCode = ex.ErrorCode;
}
await SendReponseToClientAsync(req, statusCode);
}
public async Task SendMessageAsync(Message msg)
{
try
{
// Some async operations done here
}
catch (MyCustomException ex)
{
await InsertFailureForMessageInDbAsync(msg, ex.ErrorCode); // CAN'T DO THAT
throw;
}
}
Run Code Online (Sandbox Code Playgroud)
当然,请求处理层对DB一无所知,它只负责构建消息,将消息传递给消息处理层,并向客户端发送响应(正面或负面).
所以我认为这是有意义的......如果发生异常,我的"业务"层想要在DB中插入失败记录并重新抛出异常,以便"请求"处理层可以做必要的事情(在此上下文中,发送一个消极反应回到客户端).
是否应该以这种方式使用例外情况?这对我来说似乎很干净但是我在catch子句中无法做到这一点的事实让我觉得设计中可能存在代码味道(即使我想在一层中处理异常然后重新抛出它用于上层做了一些不同的处理,对我来说听起来就像它正是为什么做的例外).
这有什么想法吗?
谢谢 !
我目前正在执行我开发的服务器应用程序的一些基准测试,严重依赖于C#5 async/await构造.
这是一个Console应用程序,因此没有同步上下文,并且代码中没有显式创建任何线程.应用程序尽可能快地从MSMQ队列中出列请求(异步出列循环),并在通过HttpClient发送处理过的请求之前处理每个请求.
依赖async/await的I/O从MSMSQ中出列,读取数据/写入数据到SQL服务器DB,最后在链的末尾发送HttpClient请求.
目前,对于我的基准测试,DB完全伪造(结果直接通过Task.FromResult返回)并且HttpClient也伪造(等待0-50 ms之间的随机Task.Delay并返回响应),所以真正的只有I/O是MSMQ的队列.
通过看到GC中花费了大量时间,我已经大大提高了应用程序的吞吐量,所以我使用了CLR Profiler并找到了我可以优化的地方.
我现在正试图看看我是否仍然可以提高吞吐量,我认为这可能是可能的.
有两件事我不明白,也许这背后有一些改进的可能性:
1)我有4个CPU核心(实际上只有2个真正的... i7 CPU),当应用程序运行时,它最多只使用3个CPU核心(在VS2012并发可视化器中我可以清楚地看到只有3个核心是正在使用,并在Windows perfmon我可以看到CPU使用率偷看~75/80%).知道为什么吗?我无法控制线程,因为我没有明确地创建它们,只依赖于Tasks,那么为什么Task调度程序在我的情况下不会最大化CPU使用率?有人经历过吗?
2)使用VS2012 concurency visualizer,我可以看到非常高的同步时间(约20%执行和80%同步).仅供参考约15个主题正在创建中.
大约60%的同步来自以下调用堆栈:
clr.dll!ThreadPoolMgr::WorkerThreadStart
clr.dll!CLRSemaphore::Wait
kernelbase.dll!WaitForSingleObjectEx
Run Code Online (Sandbox Code Playgroud)
和
clr.dll!ThreadPoolMgr::WorkerThreadStart
clr.dll!ThreadPoolMgr::UnfairSemaphore::Wait
clr.dll!CLRSemaphore::Wait
kernelbase.dll!WaitForSingleObjectEx
Run Code Online (Sandbox Code Playgroud)
大约30%的同步来自:
clr.dll!ThreadPoolMgr::CompletionPortThreadStart
kernel32.dll!GetQueueCompletionStatusStub
kernelbase.dll!GetQueuedCompletionStatus
ntdll.dll!ZwRemoveIoCompletion
..... blablabla
ntoskrnl.exe!KeRemoveQueueEx
Run Code Online (Sandbox Code Playgroud)
我不知道这种高同步是否正常.
编辑:基于斯蒂芬回答,我正在添加有关我的实现的更多细节:
的确,我的服务器完全异步.然而,一些CPU工作是为了处理每条消息(不是我承认,但仍然有一些).从MSMQ队列收到消息后,它首先被反序列化(大部分CPU /内存成本似乎在此时发生),然后它经过处理/验证的各个阶段,这需要一些CPU,最后到达"结束"管道"通过HttpClient将处理过的消息发送到外部世界的位置.
我的实现不是在从队列中出列下一个消息之前等待消息被完全处理.实际上,我的消息泵,从队列中出列消息,非常简单,并立即"转发"消息,以便能够使下一个消息出列.简化的代码如下所示(省略异常管理,取消...):
while (true)
{
var message = await this.queue.ReceiveNextMessageAsync();
this.DeserializeDispatchMessageAsync();
}
private async void DeserializeDispatchMessageAsync()
{
// Immediately yield to avoid blocking the asynchronous messaging pump
// while deserializing the body which would otherwise impact the throughput.
await Task.Yield(); …Run Code Online (Sandbox Code Playgroud) c# ×10
async-await ×4
performance ×3
.net ×1
.net-4.5 ×1
appender ×1
asynchronous ×1
c#-5.0 ×1
concurrency ×1
foreign-keys ×1
jenkins ×1
log4net ×1
msmq ×1
overloading ×1
sockets ×1
tcpclient ×1
terminology ×1
xunit.net ×1