C# NLog的线程安全性如何?
那么 我已经等了好几天才决定发表这篇文章,因为我不知道该如何陈述这一点,于是又写了一篇详细的文章。然而,我认为在这一点上寻求社区的帮助是相关的 基本上,我尝试使用NLog为数百个线程配置记录器。我认为这将非常简单,但我在几十秒后得到了这个异常: “InvalidOperationException:集合已修改;枚举操作可能无法执行” 这是代码C# NLog的线程安全性如何?,c#,multithreading,logging,nlog,C#,Multithreading,Logging,Nlog,那么 我已经等了好几天才决定发表这篇文章,因为我不知道该如何陈述这一点,于是又写了一篇详细的文章。然而,我认为在这一点上寻求社区的帮助是相关的 基本上,我尝试使用NLog为数百个线程配置记录器。我认为这将非常简单,但我在几十秒后得到了这个异常: “InvalidOperationException:集合已修改;枚举操作可能无法执行” 这是代码 //Launches threads that initiate loggers class ThreadManager { //(...)
//Launches threads that initiate loggers
class ThreadManager
{
//(...)
for (int i = 0; i<500; i++)
{
myWorker wk = new myWorker();
wk.RunWorkerAsync();
}
internal class myWorker : : BackgroundWorker
{
protected override void OnDoWork(DoWorkEventArgs e)
{
// "Logging" is Not static - Just to eliminate this possibility
// as an error culprit
Logging L = new Logging();
//myRandomID is a random 12 characters sequence
//iLog Method is detailed below
Logger log = L.iLog(myRandomID);
base.OnDoWork(e);
}
}
}
public class Logging
{
//ALL THis METHOD IS VERY BASIC NLOG SETTING - JUST FOR THE RECORD
public Logger iLog(string loggerID)
{
LoggingConfiguration config;
Logger logger;
FileTarget FileTarget;
LoggingRule Rule;
FileTarget = new FileTarget();
FileTarget.DeleteOldFileOnStartup = false;
FileTarget.FileName = "X:\\" + loggerID + ".log";
AsyncTargetWrapper asyncWrapper = new AsyncTargetWrapper();
asyncWrapper.QueueLimit = 5000;
asyncWrapper.OverflowAction = AsyncTargetWrapperOverflowAction.Discard;
asyncWrapper.WrappedTarget = FileTarget;
//config = new LoggingConfiguration(); //Tried to Fool NLog by this trick - bad idea as the LogManager need to keep track of all config content (which seems to cause my problem;
config = LogManager.Configuration;
config.AddTarget("File", asyncWrapper);
Rule = new LoggingRule(loggerID, LogLevel.Info, FileTarget);
lock (LogManager.Configuration.LoggingRules)
config.LoggingRules.Add(Rule);
LogManager.Configuration = config;
logger = LogManager.GetLogger(loggerID);
return logger;
}
}
//启动启动记录器的线程
类线程管理器
{
//(...)
对于(int i=0;i我认为您可能没有正确使用lock
。您正在锁定传递到方法中的对象,因此我认为这意味着不同的线程可能会锁定不同的对象
大多数人只是将此添加到他们的类中:
private static readonly object Lock = new object();
然后锁定它,并保证它始终是同一个对象
我不确定这是否能解决你的问题,但你现在所做的对我来说很奇怪。
其他人可能会不明白。我不知道NLog,但从上面的片段和API文档中我可以看到(http://nlog-project.org/help/),只有一个静态配置。因此,如果您希望仅在创建记录器时使用此方法将规则添加到配置中(每个规则来自不同的线程),您正在编辑相同的配置对象。据我在NLog文档中看到的,没有办法为每个记录器使用单独的配置,因此您需要所有规则。
添加规则的最佳方法是在启动异步工作程序之前添加规则,但我假设这不是您想要的。
也可以只对所有工作人员使用一个记录器,但我假设您需要将每个工作人员都放在一个单独的文件中。
如果每个线程都在创建自己的记录器并将自己的规则添加到配置中,则必须对其进行锁定。请注意,即使同步代码,在更改规则时,仍有可能有其他代码正在枚举这些规则。如您所示,NLog不会对这些代码位进行锁定。So我假设任何线程安全声明都只针对实际的日志写入方法。
我不确定你现有的锁是做什么的,但我不认为它没有达到你的目的。所以,改变吧
...
lock (LogManager.Configuration.LoggingRules)
config.LoggingRules.Add(Rule);
LogManager.Configuration = config;
logger = LogManager.GetLogger(loggerID);
return logger;
到
请注意,仅锁定您“拥有”的对象(即您的类专用的对象)被视为最佳做法。这可以防止某些类在其他代码中锁定(不符合最佳做法)锁定同一代码,这可能会造成死锁。因此,我们应该将privateConfigLock
定义为类的私有。我们还应该将其设置为静态,以便每个线程都看到相同的对象引用,如下所示:
public class Logging{
// object used to synchronize the addition of config rules and logger creation
private static readonly object privateConfigLock = new object();
...
我对你的问题没有真正的答案,但我有一些观察和问题:
根据您的代码,您似乎希望为每个线程创建一个记录器,并且希望将该记录器日志记录到一个以传入的id值命名的文件中。因此,id为“abc”的记录器将记录到“x:\abc.log”,“def”将记录到“x:\def.log”,等等。我怀疑您可以通过NLog配置而不是通过编程来实现这一点。我不知道它是否会工作得更好,或者NLog是否会出现与您相同的问题
我的第一印象是,您正在做大量的工作:为每个线程创建一个文件目标,为每个线程创建一个新规则,获取一个新的记录器实例,等等,您可能不需要这样做就可以完成您想要完成的事情
我知道NLog允许根据至少一些NLog LayoutRenderer动态命名输出文件。例如,我知道这是可行的:
fileName="${level}.log"
并将为您提供如下文件名:
Trace.log
Debug.log
Info.log
Warn.log
Error.log
Fatal.log
fileName="${mdc:myid}.log"
fileName="${basedir}/${mdc:myid}.log"
aaaaaaaaaaaa.log
aaaaaaaaaaab.log
aaaaaaaaaaac.log
例如,您可以使用这样的模式基于线程id创建输出文件:
fileName="${threadid}.log"
如果您最终拥有线程101和102,那么您将拥有两个日志文件:101.log和102.log
在您的情况下,您希望根据自己的id命名文件。您可以将id存储在MappedDiagnosticContext(这是一个允许您存储线程本地名称-值对的字典)中,然后在模式中引用该id
文件名的模式如下所示:
Trace.log
Debug.log
Info.log
Warn.log
Error.log
Fatal.log
fileName="${mdc:myid}.log"
fileName="${basedir}/${mdc:myid}.log"
aaaaaaaaaaaa.log
aaaaaaaaaaab.log
aaaaaaaaaaac.log
因此,在代码中,您可以执行以下操作:
public class ThreadManager
{
//Get one logger per type.
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
protected override void OnDoWork(DoWorkEventArgs e)
{
// Set the desired id into the thread context
NLog.MappedDiagnosticsContext.Set("myid", myRandomID);
logger.Info("Hello from thread {0}, myid {1}", Thread.CurrentThread.ManagedThreadId, myRandomID);
base.OnDoWork(e);
//Clear out the random id when the thread work is finished.
NLog.MappedDiagnosticsContext.Remove("myid");
}
}
类似的内容应该允许您的ThreadManager类有一个名为“ThreadManager”的记录器。每次记录消息时,它都会在Info调用中记录格式化字符串。如果记录器配置为记录到文件目标(在配置文件中),则制定发送“*.ThreadManager”的规则指向文件名布局如下的文件目标:
Trace.log
Debug.log
Info.log
Warn.log
Error.log
Fatal.log
fileName="${mdc:myid}.log"
fileName="${basedir}/${mdc:myid}.log"
aaaaaaaaaaaa.log
aaaaaaaaaaab.log
aaaaaaaaaaac.log
在记录消息时,NLog将根据文件名布局的值(即,它在日志时应用格式化标记)确定文件名应该是什么。如果文件存在,则将消息写入其中。如果文件尚不存在,则创建文件并将消息记录到其中
如果每个线程都有一个随机id,如“aaaaaaaaaaaa”、“aaaaaaaaaaaab”、“aaaaaaaaaaaaaaaaaac”,那么您应该得到如下日志文件:
Trace.log
Debug.log
Info.log
Warn.log
Error.log
Fatal.log
fileName="${mdc:myid}.log"
fileName="${basedir}/${mdc:myid}.log"
aaaaaaaaaaaa.log
aaaaaaaaaaab.log
aaaaaaaaaaac.log
等等
如果您可以这样做,那么您的生活应该会更简单,因为您不必对NLog进行所有编程配置(创建规则和文件目标)。而且您可以让NLog担心创建输出文件名
我不确定这是否会比您以前做的更好。或者,即使这样,您可能真的需要在更大的范围内了解您正在做的事情。测试它应该足够容易,以查看它是否有效(即,您可以根据MappedDiagnosticContext中的值命名输出文件)。如果它能解决这个问题,那么您可以在创建数千个线程的情况下尝试它。