2

我刚刚写的一些代码如下。

它演示了将 PostSharp aspect 应用于方法,以便以异步方式记录方法调用的持续时间 - 因此,如果日志记录过程很慢,则使用该 aspect 装饰的方法的调用者不会看到这种性能损失.

它似乎可以工作,随着 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);
        }
    }
}
4

2 回答 2

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 并且单个任务处理图像。

并行任务库 WaitAny Design

这在某种程度上取决于您的请求处理的长度、每个请求要处理的日志条目数以及整体服务器负载等。您必须注意的一件事是,总体而言,您需要能够从队列中删除请求比添加请求更快。

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

于 2010-08-03T00:24:36.717 回答
2

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

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

这类似于 Producer/Consumer 模式,您的代码生成日志消息,而消费者负责将这些消息刷新到持久性介质。

于 2010-07-10T11:59:19.757 回答