【问题标题】:Is this a safe and *relatively okay* way to log certain events asynchronously?这是异步记录某些事件的安全且*相对好的*方法吗?
【发布时间】:2014-04-10 23:38:39
【问题描述】:

使用:

  • .NET 4.5.1
  • 网络表单
  • 实体框架 6(每个请求的上下文)
  • IIS 8、Windows 2012 数据中心

主要关注点:线程安全性和可靠性。

该项目由一个封闭的系统组成,可供多种类型的用户使用,这些用户将执行各种操作。在我们当前的项目中,我决定实现一些可能大多数开发人员认为绝对必要的东西。

过去,由于缺乏最简单的日志系统,我们无法跟踪一些常见的用户操作,尤其是操纵数据,因此存在严重的问题。

我知道有流行的日志框架,但我想实现一些相对简单的不会阻塞主线程的东西。

我的想法是在主线程上提取我需要的所有数据,因为事实证明有些数据更难从单独的线程访问,然后创建一个任务来处理数据库插入。我对多线程的了解是有限的,这就是我想出的。

public static class EventLogger
    {
        public static void LogEvent(int eventType, string descr, Exception ex = null)
        {
            //Get the page that is currently being executed;
            var executingPage = HttpContext.Current.CurrentHandler as Page;
            string sourcePage = executingPage != null ? executingPage.AppRelativeVirtualPath : string.Empty;

            var eventToAdd = new Event()
            {
                Date = DateTime.Now,
                EventTypeId = eventType,
                isException = ex != null ? true : false,
                ExceptionDetails = ex != null ? ex.Message : string.Empty,
                Source = sourcePage,
                UserId = UserHelper.GetUserGuid(),
                UserIP = UserHelper.GetUserIP(),
                UserName = UserHelper.GetUserName(),
                Description = descr
            };

            Task.Factory.StartNew(() => LogEventAsync(eventToAdd));
        }

        private static void LogEventAsync(Event eventToAdd)
        {
            using (var context = new PlaceholderEntities())
            {
                context.Events.Add(eventToAdd);
                context.SaveChanges();
            }
        }
    }

问题:

  • 这是一种足够好的方式来记录我需要的内容吗?在多用户环境中是否安全?
  • 如果您不想深入研究日志框架,您会怎么做?

【问题讨论】:

标签: c# asp.net multithreading entity-framework


【解决方案1】:

是的,它在多线程环境中是安全的。因为每次将事件插入数据库时​​都会创建数据库上下文的新实例,所以您是安全的。仅当您尝试跨线程重用相同的上下文实例时,EF 才不是线程安全的。

唯一可能的问题是,以异步方式执行此操作可能意味着同时打开多个连接并且连接池可能会被耗尽。您登录的次数越多,发生这种情况的可能性就越高。

话虽如此,我仍然建议您使用 log4net 或任何现有的日志记录基础设施,并找到一种异步记录的方法。人们经常写博客如何使用 log4net 做到这一点,例如看看这里

http://www.ben-morris.com/using-asynchronous-log4net-appenders-for-high-performance-logging

请注意,此处还讨论了与异步日志记录相关的问题。要解决某些问题,例如条目排序错误的可能性,您可能会在此类自定义附加程序中设置一个队列。坚持现有的框架可以让你重用很多已经开发的想法。当需求发生变化时,迟早要靠自己做事会让你停顿更久。

【讨论】:

    【解决方案2】:

    正如@gbjbaanb 所提到的,您应该将其实现为一个队列,其中一个或多个线程实际上通过 EF 执行数据库工作。您可以使用由ConcurrentQueue<T> 支持的BlockingCollection<T> 以生产者/消费者的方式执行此操作。基本前提是每个调用者记录的东西只是添加到队列中(即生产者)。然后,您可以让一个或多个线程从队列中提取信息并持久保存到数据库(即消费者)。每个线程都需要一个单独的上下文。

    BlockingCollection<T> 文档页面上有一个合理的示例。

    【讨论】:

      【解决方案3】:

      不,不是。 EF6 is not thread-safe(嗯,DBContext 不是线程安全的,所以如果每个线程有 1 个 DBcontext,你应该没问题),所以添加日志事件可能(哈!)干扰另一个线程写入不同的日志事件。

      您需要做的是同步任务中对 EF 的所有调用,我要做的是将日志添加到集合中,然后让另一个线程将这些日志从集合中拉出并将它们写入数据库, 一次一个。对集合的添加/删除调用必须使用锁来保护,以阻止 2 个线程同时添加(或删除)一个条目。

      或者我会使用 log4net,is thread-safe,所以你可以简单地在你的线程中调用它来进行写入或日志条目。

      我的另一个建议是将日志写入文件而不是数据库。文件记录很快,因此您可以在主线程上以最小的性能影响执行它(log4net 也非常有效,所以只需使用它 - 我知道 IIS 无论如何都会将访问和错误日​​志写入文件)这将消除您的问题带螺纹。

      我真正知道的一件事是,如果您对多线程不太感兴趣,那么您需要停止使用它,直到您对它感兴趣为止。没有不尊重您的尝试,但是线程错误可能是要解决的噩梦,您无法重现它们,也无法在调试器中轻松捕获它们,而且它们的原因通常与报告的症状无关。

      【讨论】:

      • 您能否详细说明为什么 Entity Framework 不是线程安全的,我可能是错的,但我是这样看的 -> 创建一个新线程,创建一个新的独立上下文,做数据库工作 ->喝杯咖啡。这不应该是线程安全的,因为它们是两个不同且独立的上下文吗?
      • 在我发表评论后对此进行了一些研究,看来创建多个上下文是可行的方法,并且不应该引起任何问题。尝试从多个线程中使用相同的上下文会导致您所建议的问题。
      • entityframework.codeplex.com/…。表示他们明确没有尝试在 EF6 中启用线程安全。很多上下文可能是一个性能问题,特别是如果您的所有线程都没有快速完成并且您继续添加越来越多的线程。线程池是该问题的解决方案——但是你又回到了我使用队列的建议。
      • 然后我会考虑队列解决方案,只需要考虑如何正确实施它。如果没有,我预计系统中的并发用户不会超过 200 个,因此如果我未能正确实施您的建议,创建线程和单独的日志记录上下文可能就足够了。同样对于多线程的热或不太热,我知道可能由于缺乏经验而导致的最终问题,但是如果我不尝试并遇到这些问题,我应该怎么做学习?
      • @Null 学习.. 通过犯错 :) 只需记住锁定队列的读取和写入,以便它始终处于理智状态。我还将添加一个性能计数器来跟踪未完成的排队请求,以便您可以查看它们是被快速写入还是正在备份。我还将确保在停止服务之前队列清空(否则您将丢失未写入的日志),并可能在主线程中捕获 all 异常,以便日志线程有时间编写日志消息告诉您为什么会崩溃……尽管我不会做任何事情-我会立即登录到文件。祝你好运
      猜你喜欢
      • 1970-01-01
      • 2021-02-08
      • 1970-01-01
      • 2010-12-21
      • 2017-06-05
      • 2013-11-20
      • 1970-01-01
      • 1970-01-01
      • 2018-03-02
      相关资源
      最近更新 更多