NLog 的线程安全性如何?

2024-04-20

Well,

我等了好几天才决定发布这个问题,因为我不知道如何陈述这一点,结果写了一篇很长的详细帖子。不过,我认为此时向社区寻求帮助是有意义的。

基本上,我尝试使用 NLog 为数百个线程配置记录器。我认为这会非常简单,但几十秒后我得到了这个异常: ”InvalidOperationException:集合已修改;枚举操作可能无法执行"

这是代码。

//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;
    }
}   

所以我完成了我的工作,而不只是在这里发布我的问题并享受家庭质量的时光,我花了周末的时间来挖掘这个问题(幸运的男孩!) 我下载了 NLOG 2.0 的最新稳定版本并将其包含在我的项目中。我能够追踪到它爆炸的确切位置:

在 LogFactory.cs 中:

    internal void GetTargetsByLevelForLogger(string name, IList<LoggingRule> rules, TargetWithFilterChain[] targetsByLevel, TargetWithFilterChain[] lastTargetsByLevel)
    {
        //lock (rules)//<--Adding this does not fix it
            foreach (LoggingRule rule in rules)//<-- BLOWS HERE
            {
            }
     }

在 LoggingConfiguration.cs 中:

internal void FlushAllTargets(AsyncContinuation asyncContinuation)
    {            
        var uniqueTargets = new List<Target>();
        //lock (LoggingRules)//<--Adding this does not fix it
        foreach (var rule in this.LoggingRules)//<-- BLOWS HERE
        {
        }
     }

根据我的问题
因此,根据我的理解,发生的情况是 LogManager 混淆了,因为有从不同线程调用 config.LoggingRules.Add(Rule) while GetTargetsByLevelForLogger and 刷新所有目标正在被召唤。 我尝试修改 foreach 并将其替换为 for 循环,但记录器变得流氓(跳过许多日志文件创建)

SOoooo FINALLY
到处都写着 NLOG 是线程安全的,但我浏览了一些帖子,进一步深入研究并声称这取决于使用场景。我的情况呢? 我必须创建数千个记录器(不是同时创建,但仍然以非常高的速度)。

我发现的解决方法是在同一个主线程中创建所有记录器;这是REALLY不方便,因为我在应用程序启动时创建了所有应用程序记录器(类似于记录器池)。 尽管它工作起来很甜蜜,但这并不是一个可以接受的设计。

所以你们都知道了。 请帮助一名程序员再次见到他的家人。


我对你的问题并没有真正的答案,但我确实有一些观察和一些问题:

根据您的代码,您似乎想为每个线程创建一个记录器,并且希望将该记录器记录到以某些传入的 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

因此,例如,您似乎可以使用这样的模式根据线程 ID 创建输出文件:

fileName="${threadid}.log"

如果您最终拥有线程 101 和 102,那么您将拥有两个日志文件:101.log 和 102.log。

在你的例子中,你想根据你自己的 id 来命名文件。您可以将 id 存储在 MappedDiagnosticContext (这是一个字典,允许您存储线程本地名称-值对),然后在您的模式中引用它。

您的文件名模式将如下所示:

fileName="${mdc:myid}.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”发送到文件目标,其文件名布局如下所示:

fileName="${basedir}/${mdc:myid}.log"

记录消息时,NLog 将根据 fileName 布局的值确定文件名应该是什么(即,它在记录时应用格式化标记)。如果该文件存在,则消息将写入其中。如果该文件尚不存在,则会创建该文件并将消息记录到该文件中。

如果每个线程都有一个随机 ID,如“aaaaaaaaaaaa”、“aaaaaaaaaaab”、“aaaaaaaaaaac”,那么您应该获得如下日志文件:

aaaaaaaaaaaa.log
aaaaaaaaaaab.log
aaaaaaaaaaac.log

等等。

如果您可以这样做,那么您的生活应该会更简单,因为您不必进行 NLog 的所有编程配置(创建规则和文件目标)。您可以让 NLog 负责创建输出文件名。

我不确定这是否会比你所做的更好。或者,即使确实如此,您可能确实需要从更大的角度考虑您正在做的事情。测试它是否有效应该很容易(即您可以根据 MappedDiagnosticContext 中的值命名输出文件)。如果它适用于此,那么您可以在创建数千个线程的情况下尝试它。

UPDATE:

这是一些示例代码:

使用该程序:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using NLog;
using System.Threading;
using System.Threading.Tasks;

namespace NLogMultiFileTest
{
  class Program
  {
    public static Logger logger = LogManager.GetCurrentClassLogger();

    static void Main(string[] args)
    {

      int totalThreads = 50;
      TaskCreationOptions tco = TaskCreationOptions.None;
      Task task = null;

      logger.Info("Enter Main");

      Task[] allTasks = new Task[totalThreads];
      for (int i = 0; i < totalThreads; i++)
      {
        int ii = i;
        task = Task.Factory.StartNew(() =>
        {
          MDC.Set("id", "_" + ii.ToString() + "_");
          logger.Info("Enter delegate.  i = {0}", ii);
          logger.Info("Hello! from delegate.  i = {0}", ii);
          logger.Info("Exit delegate.  i = {0}", ii);
          MDC.Remove("id");
        });

        allTasks[i] = task;
      }

      logger.Info("Wait on tasks");

      Task.WaitAll(allTasks);

      logger.Info("Tasks finished");

      logger.Info("Exit Main");
    }
  }
}

这个 NLog.config 文件:

<?xml version="1.0" encoding="utf-8" ?>
<!-- 
  This file needs to be put in the application directory. Make sure to set 
  'Copy to Output Directory' option in Visual Studio.
  -->
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <targets>
        <target name="file" xsi:type="File" layout="${longdate} | ${processid} | ${threadid} | ${logger} | ${level} | id=${mdc:id} | ${message}" fileName="${basedir}/log_${mdc:item=id}.txt" />
    </targets>

    <rules>
        <logger name="*" minlevel="Debug" writeTo="file" />
    </rules>
</nlog>

我能够为委托的每次执行获取一个日志文件。日志文件以 MDC (MappedDiagnosticContext) 中存储的“id”命名。

因此,当我运行示例程序时,我得到了 50 个日志文件,每个日志文件都有三行“Enter...”、“Hello...”、“Exit...”。每个文件都有命名log__X_.txt其中 X 是捕获的计数器 (ii) 的值,所以我有 log_0.txt,日志_1.txt,日志_1.txt等,log_49。TXT。每个日志文件仅包含与委托的一次执行相关的日志消息。

这与您想要做的类似吗?我的示例程序使用任务而不是线程,因为我之前已经编写过它。我认为该技术应该足够容易地适应你正在做的事情。

您也可以使用相同的 NLog.config 文件以这种方式(为委托的每次执行获取一个新的记录器):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using NLog;
using System.Threading;
using System.Threading.Tasks;

namespace NLogMultiFileTest
{
  class Program
  {
    public static Logger logger = LogManager.GetCurrentClassLogger();

    static void Main(string[] args)
    {

      int totalThreads = 50;
      TaskCreationOptions tco = TaskCreationOptions.None;
      Task task = null;

      logger.Info("Enter Main");

      Task[] allTasks = new Task[totalThreads];
      for (int i = 0; i < totalThreads; i++)
      {
        int ii = i;
        task = Task.Factory.StartNew(() =>
        {
          Logger innerLogger = LogManager.GetLogger(ii.ToString());
          MDC.Set("id", "_" + ii.ToString() + "_");
          innerLogger.Info("Enter delegate.  i = {0}", ii);
          innerLogger.Info("Hello! from delegate.  i = {0}", ii);
          innerLogger.Info("Exit delegate.  i = {0}", ii);
          MDC.Remove("id");
        });

        allTasks[i] = task;
      }

      logger.Info("Wait on tasks");

      Task.WaitAll(allTasks);

      logger.Info("Tasks finished");

      logger.Info("Exit Main");
    }
  }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

NLog 的线程安全性如何? 的相关文章

  • 如何在 Python 中的函数入口、内部和退出处进行日志记录

    我希望能够使用 Python 日志记录工具在我的代码中进行简单且一致的日志记录 我能够执行以下操作 我希望所有现有 未来的模块和函数都有 输入 和 完成 日志消息 我不想添加相同的代码片段来定义日志记录参数 如下所示don t want t
  • 从另一个 FORM 中取回隐藏的 FORM

    我有两种形式Form1 and Form2 我正在打开Form2 from Form1 on button Click Form2 obj2 new Form2 this Visible false obj2 Show 然后我想回来Form
  • OpenGL缓冲区更新[重复]

    这个问题在这里已经有答案了 目前我正在编写一个模拟水的程序 以下是我所做的步骤 创建水面 平面 创建VAO 创建顶点缓冲区对象 在其中存储法线和顶点 将指针绑定到此 VBO 创建索引缓冲区对象 然后我使用 glDrawElements 渲染
  • MSMQ接收和删除

    是否有任何选项可以在读取消息后将其从 MSMQ 中删除 比如 接收 删除可以作为原子操作运行吗 听起来您想查看下一条消息 然后在处理完成后接收它 Message message Queue Peek Queue ReceiveById me
  • 如何查明 .exe 是否正在 C++ 中运行?

    给定进程名称 例如 程序 exe C 标准库没有这样的支持 您需要一个操作系统 API 来执行此操作 如果这是 Windows 那么您将使用 CreateToolhelp32Snapshot 然后使用 Process32First 和 Pr
  • 以下 PLINQ 代码没有改进

    我没有看到使用以下代码的处理速度有任何改进 IEnumerable
  • 有一种简单的方法可以忽略时间戳来区分日志文件吗?

    我需要比较两个日志文件 但忽略每行的时间戳部分 确切地说是前 12 个字符 有没有一个好的工具 或者一个聪明的 awk 命令 可以帮助我 根据您使用的 shell 您可以改变方法 Blair https stackoverflow com
  • 判断串口是普通COM还是SPP

    我正在寻找一种方法来确定 COM 是标准 COM 还是 SPP COM 也称为 COM 设备的电缆替换蓝牙适配器 我有一个可以在 USB COM gt USB 和蓝牙下工作的设备 并且蓝牙接口可以与 SPP 一起工作 我目前正在使用Syst
  • 名称查找、实例化点 (POI) 和基本类型

    以下代码针对 X 进行编译 但不适用于 double struct X void foo double void foo X namespace NN struct A void foo A foo double error foo not
  • 是否可以在Linux上将C转换为asm而不链接libc?

    测试平台为Linux 32位 但也欢迎 Windows 32 位上的某些解决方案 这是一个c代码片段 int a 0 printf d n a 如果我使用 gcc 生成汇编代码 gcc S test c 然后我会得到 movl 0 28 e
  • 如何使用 C# 查询远程 MS ACCESS .mdb 数据库

    我正在尝试使用 C 查询 mote MS ACCESS 数据库 mdb 文件 将文件复制到本地计算机时可以成功查询它 我只想远程放置文件 所以我的客户端程序不包含原始数据 static string m path http www xyz
  • 在 C++ 代码 gdb 中回溯指针

    我在运行 C 应用程序时遇到段错误 在 gdb 中 它显示我的一个指针位置已损坏 但我在应用程序期间创建了 10 万个这样的对象指针 我怎样才能看到导致崩溃的一个 我可以在 bt 命令中执行任何操作来查看该指针的生命周期吗 谢谢 鲁奇 据我
  • 使用单独的线程在java中读取和写入文件

    我创建了两个线程并修改了 run 函数 以便一个线程读取一行 另一个线程将同一行写入新文件 这种情况会发生直到整个文件被复制为止 我遇到的问题是 即使我使用变量来控制线程一一执行 但线程的执行仍然不均匀 即一个线程执行多次 然后控制权转移
  • 在 mvc4 中创建通用 mvc 视图

    我以前也提过类似的问题 没有得到答案 如何创建一个通用的 mvc4 视图 该视图可以显示传递给它的模型列表或单个模型 模型可以是个人 组织或团体 无论传递给它的是什么 如果您正在寻找类似的东西 model MyViewModel
  • WPF DataGrid - 在每行末尾添加按钮

    我想在数据网格的每一行的末尾添加一个按钮 我找到了以下 xaml 但它将按钮添加到开头 有人知道如何在所有数据绑定列之后添加它吗 这会将按钮添加到开头而不是末尾
  • 如何测试某些代码在 C++ 中无法编译? [复制]

    这个问题在这里已经有答案了 可能的重复 单元测试编译时错误 https stackoverflow com questions 605915 unit test compile time error 我想知道是否可以编写一种单元测试来验证给
  • 与 Entity Framework Core 2.0 的一对零关系

    我正在使用 C 和 NET Framework 4 7 将 Entity Framework 6 1 3 Code First 库迁移到 Entity Framework Core 我一直在用 Google 搜索 Entity Framew
  • 当 Verb="runas" 时设置 ProcessStartInfo.EnvironmentVariables

    我正在开发一个 C 应用程序 我需要创建变量并将其传递给新进程 我正在使用ProcessStartInfo EnvironmentVariables 新进程必须提升运行 因此我使用 Verb runas var startInfo new
  • 解释这段代码的工作原理;子进程如何返回值以及在哪里返回值?

    我不明白子进程如何返回该值以及返回给谁 输出为 6 7 问题来源 http www cs utexas edu mwalfish classes s11 cs372h hw sol1 html http www cs utexas edu
  • 如何在c中断言两个类型相等?

    在 C 中如何断言两种类型相等 在 C 中 我会使用 std is same 但搜索 StackOverflow 和其他地方似乎只能给出 C 和 C 的结果 在C中没有办法做到这一点吗 请注意 这不是询问变量是否具有某种类型 而是询问两个类

随机推荐

  • TypeScript 中的语音识别和语音合成

    我能够通过创建如下接口在 TypeScript 中运行 SpeechRecognition 并且工作正常 namespace CORE export interface IWindow extends Window webkitSpeech
  • Java 写入 Windows Server 2016 时文件上次修改时间未更新

    我在 Windows Server 2016 上有一个 Java 10 应用程序 它不断使用 java util logging 写入文件 在 Windows 文件资源管理器中 上次修改 和 大小 列不会更新 按 F5 不会更新详细信息 操
  • 当我们手动将tomcat作为Windows服务运行时,如何更改tomcat的java_opts?

    我在控制台上手动运行 tomcat 6 作为 Windows 服务 我需要在启动之前更改 java opts 我怎么做 另外 有没有办法可以动态查看日志 我知道这是一个旧线程 但需要纠正一些假设 仅供参考 当将 tomcat 作为服务运行时
  • 替换数据框中的重复列

    我有一个data frame in pyspark 该数据框有一些带有特殊字符的列 cols df schema names cols abc test test abc eng test abc test reps def col ren
  • JSON 模式中的小数精度

    我想让我的 JSON 架构验证发送到我的 REST api 的小数位数不超过两位 从我在最新的 JSON Schema RFC v4 中看到的情况来看 不允许这样做 V1 有一个 maxDecimals 验证器 有谁知道为什么被删除 我有一
  • 如何从 Asp Label.Text 加载航路点坐标

    我在其中硬核值的第一个代码
  • 未使用重定向设置实例变量

    什么会导致我的实例变量 product 无法为重定向设置 传递 Product 是一个 ActiveModel 对象 而不是 ActiveRecord 更具体地说 product 变量没有出现在redirect to new service
  • VSCode 文件夹结构

    我想更改文件夹结构 如果您在文件夹 utils 中看到我有另一个名为 mocks 的文件夹 我想要的是更改结构以按文件夹查看1个文件夹 类似这样 就像只有 1 个文件的 services 文件夹一样 我有相同的结构 另一个文件夹中有 1 个
  • 如何从继承的 FromBody 模型中获取正确的类型?

    正文的帖子有几种不同的 XML 传入 所有 XMLS 几乎都是相同的 因此我首先添加一个基类 其他 XMLS 继承自该基类 这是模型 XmlInclude typeof TextMsg XmlRoot xml public class Ba
  • 检查属性是否用特定注释修饰 - Typescript

    如何确定特定属性是否用特定注释修饰 例如这个类 class A DecoratedWithThis thisProp number 我怎么知道thisProp装饰有DecoratedWithThis 我的用例 我使用另一个文件中的类来生成属
  • Android 指纹原始数据

    Android 的指纹传感器实现是否支持直接访问原始指纹数据 即手指上的实际图案 我并不是指用于解锁设备和付款的存储的安全指纹 而是指在扫描手指时按需获取原始数据 如果没有 为什么不呢 Android API 的作用not允许直接访问原始指
  • 在 Unity 中双向旋转门

    我在 Unity 中创建了一扇打开和关闭的门 我可以通过打电话打开那扇门Interact 现在我想创建一扇始终远离玩家打开的门 就像酒吧的门一样 如果玩家在房间前面 门就会旋转到房间 如果玩家在房间里 门就会旋转出去 目前我创建了一个布尔值
  • Python - AttributeError:“NoneType”对象没有属性“findAll”

    我已经编写了第一段 python 代码来抓取网站 import csv import urllib2 from BeautifulSoup import BeautifulSoup c csv writer open data csv wb
  • 尝试将 span 元素设置为等于 JS 石头剪刀布游戏中的变量值

    我正在编写一个玩石头 剪刀 布的程序 当我编码时 一切都很顺利 直到我添加了 userScore span InnerHTML userScore 线 在测试 win 功能时 我添加了 console log you win 它工作得很好
  • Vue.js 隐藏当前视口之外的项目

    我正在 Vue js 中制作一个电子商务类型的菜单 其中的项目是包含大量功能和图像的 div 当渲染大约 200 个这样的项目时 性能相当不错 但是当添加的数量超过这个数量时 网站的性能开始变得缓慢 如果 Vue 元素位于当前可滚动视图之外
  • 为什么 Scala 程序的编译速度非常慢?

    过去两个月我一直在使用 Scala 我还在一个小应用程序中使用 Play 框架 我观察到 即使对于打印 Hello World 的程序来说 编译也非常慢 为什么这么慢 有什么减少时间的技巧吗 您的情况下编译速度有多快 scalac 的速度受
  • 无法访问 GridView 中的 HyperLinkField 文本

    我有一个 HyperLinkField 定义如下
  • 在 vs 代码编辑器中隐藏代码块行

    我的所有代码上都有这些奇怪的行 它们似乎突出显示了代码块 我该如何关闭它们 我的是1 30版本 Link https ibb co z5Tt6t4 https ibb co z5Tt6t4 所以左边的白线 它们叫什么以及如何将它们关闭 您可
  • 识别不在另一个数据框中的记录

    我有一个像这样的数据框 data1 pd DataFrame a z 0 a y 20 b z 1 columns id1 id2 number data2 pd DataFrame a y 1 a y 1 b z 0 columns id
  • NLog 的线程安全性如何?

    Well 我等了好几天才决定发布这个问题 因为我不知道如何陈述这一点 结果写了一篇很长的详细帖子 不过 我认为此时向社区寻求帮助是有意义的 基本上 我尝试使用 NLog 为数百个线程配置记录器 我认为这会非常简单 但几十秒后我得到了这个异常