使用此异步日志记录代码有什么缺点?

时间:2010-07-10 11:44:38

标签: c# postsharp task-parallel-library multithreading

我刚写的一些代码如下。

它演示了将PostSharp方面应用于方法,以便以异步方式记录方法调用的持续时间 - 这样,如果日志记录过程很慢,那么装饰方法的调用者就不会看到这种性能损失与方面。

似乎工作,MyFirstMethod完成后,日志记录方法在一个单独的线程中启动,MySecondMethod并行运行。这个想法是在一个非常高流量的Web应用程序(即高度多线程的环境)中的方法用类似的仪器装饰。

这样做有哪些陷阱? (例如,我担心在任何给定时间达到允许的线程数限制。)

using System;
using System.Threading.Tasks;
using NUnit.Framework;
using PostSharp.Aspects;

namespace Test
{
    [TestFixture]
    public class TestClass
    {        
        [Test]
        public void MyTest()
        {            
            MyFirstMethod();
            MySecondMethod();
        }

        [PerformanceInstrument]
        private void MyFirstMethod()
        {
            //do nothing
        }

        private void MySecondMethod()
        {
            for (int x = 0; x < 9999999; x++);
        }
    }

    [Serializable]
    public class PerformanceInstrument : MethodInterceptionAspect
    {                    
        public override void OnInvoke(MethodInterceptionArgs args)
        {            
            var startDtg = DateTime.Now;
            args.Proceed();
            var duration = DateTime.Now - startDtg;
            Task.Factory.StartNew(() => MyLogger.MyLoggingMethod(duration)); //invoke the logging method asynchronously
        }        
    }

    public static class MyLogger
    {
        public static void MyLoggingMethod(TimeSpan duration)
        {
            for (int x = 0; x < 9999999; x++);
            Console.WriteLine(duration);
        }
    }
}

2 个答案:

答案 0 :(得分:2)

我在这里看到的唯一可能的缺点是管理任务的开销,我相信这可能是微不足道的,但我还没有深入研究TPL的东西。

我在大型Web应用程序中使用的另一种方法是让日志记录将日志消息记录写入内存列表,然后我有一个后台线程负责在后台写出日志消息。目前,解决方案让线程每隔一段时间检查一次列表并将列表刷新到磁盘(在我们的案例数据库中),如果列表长度超过某个threashold,或者列表的刷新时间超过特定时间,则永远是第一位的。

这类似于生产者/消费者模式,您可以在其中生成日志消息,并且消费者可以将这些消息刷新到持久性介质。

答案 1 :(得分:2)

您的方法可能会产生意想不到的后果,因为ASP.NET引擎和任务并行库都是.NET线程池上的调度任务。每个Web请求都由线程池中的线程提供服务。如果您作为调度任务来处理日志记录,那么您将使用线程池上的任务,该任务不再用于为Web请求提供服务。这可能会降低吞吐量。

TPL团队在此发表了博客文章。

http://blogs.msdn.com/b/pfxteam/archive/2010/02/08/9960003.aspx

生产者/消费者模式意味着您的MethodInterceptionAspect只是将一个条目添加到全局队列中(如Ben所建议的那样),然后您将拥有一个处理所有条目的单个(长时间运行)任务。所以你的intercaption方法变成了:

ConcurrentQueue<TimeSpan> _queue;

public override void OnInvoke(MethodInterceptionArgs args)
{
    var startDtg = DateTime.Now;
    args.Proceed();
    var duration = DateTime.Now - startDtg;
    Task.Factory.StartNew(() => _queue.Add(duration)); 
}

您处理队列的其他地方:

foreach (var d in _queue.GetConsumingEnumerable())
{
    Console.WriteLine(d);
}

以下文章显示了一个类似的实现,其中由Parallel.For循环创建的多个任务将图像添加到BlockingCollection并且单个任务处理图像。

Parallel Task Library WaitAny Design

这有多好,取决于您的请求处理的长度,您希望处理每个请求的日志条目的数量以及整体服务器负载等。您需要注意的一件事是,您需要整体能够更快地从队列中删除请求。

您是否考虑过编写自己的性能计数器并让perf计数器基础设施为您处理繁重工作的方法?这将节省您需要实现任何此录制基础架构。