简体   繁体   English

在这个多头例子中,我在做什么错?

[英]What am I doing wrong in this multitheading example?

this is my first adventure into multi threading and I think I am missing some key concepts so any help will be appreciated. 这是我第一次尝试多线程,我想我缺少一些关键概念,因此我们将不胜感激。 I am trying to create a log manager for an asp.net application. 我正在尝试为asp.net应用程序创建日志管理器。 We will be logging views/inserts/modifies/deletes on a ton of data in our system. 我们将记录/插入/修改/删除我们系统中大量数据的视图。 Instead of constantly inserting rows I thought that maybe if I created a singleton to hold a list of log entries in memory until it reached a certain size then write them all to the db at once. 与其一直不插入行,我想如果我创建一个单例以在内存中保存日志条目列表,直到达到一定大小,然后将它们全部写入数据库。 Then I thought maybe running this on a new thread will improve performance when the log needs to be written. 然后我想也许在需要写入日志的时候在新线程上运行它可以提高性能。 Below is my test code. 下面是我的测试代码。 If I remove threading I get the 500 rows in the db that I expect but when I use multithreading I get around 200-300. 如果我删除线程,我会在数据库中得到500行,但是我使用多线程时大约会得到200-300。 Around half of the records aren't getting inserted. 大约一半的记录没有插入。 Is this a valid use for multithreading, and what am I doing wrong? 这对多线程有效吗?我在做什么错? Thank You. 谢谢。

LogManager: 日志管理:

  public sealed class LogManager
  {
    private static LogManager _Log = null;
    private static readonly object singletonLock = new object();
    private static readonly object listLock = new object();

    private List<LogEntry> LogEntries { get; set; }

    public static LogManager Log
    {
      get
      {
        if (_Log == null)
        {
          lock (singletonLock)
          {
            if (_Log == null)
            {
              _Log = new LogManager();
            }
          }
        }
        return _Log;
      }
    }

    public LogManager()
    {
      LogEntries = new List<LogEntry>();
    }

    public void Add(LogEntry logEntry)
    {
      lock (listLock)
      {
        LogEntries.Add(logEntry);
        if (LogEntries.Count >= 100)
        {          
          ThreadStart thread = delegate { Flush(new List<LogEntry>(LogEntries)); };
          new Thread(thread).Start();
          //Flush(LogEntries);          
          LogEntries.Clear();
        }
      }
    }

    private static void Flush(List<LogEntry> logEntries)
    {
      using (var conn = new SqlConnection(DAL.ConnectionString))
      {
        using (var cmd = conn.CreateCommand())
        {
          cmd.CommandType = CommandType.StoredProcedure;
          cmd.CommandText = "spInsertLog";
          conn.Open();
          foreach (var logEntry in logEntries)
          {
            cmd.Parameters.AddWithValue("@ID", logEntry.ID);
            try
            {
              cmd.ExecuteNonQuery();
            }
            catch (Exception ex) { throw (ex);/*KeepGoing*/}
            cmd.Parameters.Clear();
          }
        }      
      }
    }
  }

Console App: 控制台应用程序:

  class Program
  {
    static void Main(string[] args)
    {
      var stopwatch = new Stopwatch();      
      for (int i = 0; i < 500; i++)
      {
        stopwatch.Start();
        LogManager.Log.Add(new LogEntry() { ID = i });
        Console.WriteLine(String.Format("Count: {0}   Time: {1}",i.ToString(),stopwatch.ElapsedMilliseconds));
        stopwatch.Stop();
        stopwatch.Reset();
      }      
    }
  }
ThreadStart thread = delegate { Flush(new List<LogEntry>(LogEntries)); };
      new Thread(thread).Start();
      //Flush(LogEntries);          
      LogEntries.Clear();

List<LogEntry> is a reference type. List<LogEntry>是引用类型。 Your new thread starts inserting them, but then you clear that list before it is finished. 您的新线程将开始插入它们,但随后请在完成操作之前清除该列表。 When you don't use multithreading, you wait for the entire list to be flushed before you clear it. 当您不使用多线程时,您需要等待刷新整个列表后再清除它。 You can fix this by changing the signature of Flush to take an array and doing 您可以通过更改Flush的签名以获取数组并执行此操作来解决此问题

ThreadStart thread = delegate { Flush(LogEntries.ToArray()); };
      new Thread(thread).Start();
      //Flush(LogEntries);          
      LogEntries.Clear();

Before I analyze a single line of your code, your intermediary storage for Log Messages is in the wrong place. 在我分析一行代码之前,日志消息的中间存储位置错误。 I strongly recommend using MSMQ or some other queueing mechanism to store your messages while awaiting your LogManager for processing. 我强烈建议使用MSMQ或其他排队机制来存储消息,同时等待LogManager进行处理。

You are calling Flush in a separate thread and passing a reference to your list of log entries, then clearing the list in the current thread. 您在单独的线程中调用Flush,并将引用传递给日志条目列表,然后清除当前线程中的列表。 You've effectively destroyed the list of entries the new thread was supposed to log. 您已经有效地破坏了新线程应该记录的条目列表。 You need to pass a copy of the LogEntries list into the Flush thread before you clear your LogEntries field. 在清除LogEntries字段之前,需要将LogEntries列表的副本传递到Flush线程中。

Perhaps something like: 也许像这样:

{Flush(LogEntries.ToList())}

The LINQ expression ToList() will create a copy of the list for your Flush method. LINQ表达式ToList()将为Flush方法创建列表的副本。

As an aside, I would change your Flush method to take an IEnumerable<LogEntry> so that you can pass other collections, not just lists, into the method. IEnumerable<LogEntry>IEnumerable<LogEntry> ,我将您的Flush方法更改为采用IEnumerable<LogEntry>以便您可以将其他集合(不仅仅是列表)传递给该方法。

A couple of things that I see: First, I wouldn't use a List<> I would use a Queue<> , it's better suited for this situation. 我看到以下几件事:首先,我不会使用List<>我会使用Queue<> ,它更适合这种情况。 Secondly, right after you fire off the thread you clear the list. 其次,在启动线程后立即清除列表。 So there's a good chance that by the time the thread actually starts executing its code, the list is already empty. 因此,很有可能在线程实际开始执行其代码时,该列表已经为空。 A Queue<> should help solve this problem, because you can remove items from the queue as they are written to the database. Queue<>应该有助于解决此问题,因为您可以在将项目写入数据库时​​从队列中删除它们。

Also, you should be locking your code while you're accessing the list, you could get an exception if an item is added to the list while you're iterating it. 另外,在访问列表时应该锁定代码,如果在迭代过程中将项目添加到列表中,则可能会出现异常。 This would apply to a Queue<> as well, what I typically do is something like: 这也将适用于Queue<> ,我通常要做的是:

LogEntry myEntry;
lock(sync) {
   myEntry = myQueue.Dequeue();
}

Then also lock in your Add method (which you do). 然后还锁定您的Add方法(您可以执行此操作)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM