我在这个多头节目示例中做错了什么?

时间:2022-09-06 22:59:42

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. 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. Around half of the records aren't getting inserted. Is this a valid use for multithreading, and what am I doing wrong? Thank You.

这是我第一次进入多线程的冒险,我想我缺少一些关键概念,所以任何帮助将不胜感激。我正在尝试为asp.net应用程序创建一个日志管理器。我们将在系统中的大量数据上记录视图/插入/修改/删除。而不是不断地插入行,我想如果我创建一个单例来保存内存中的日志条目列表,直到达到一定大小,然后立即将它们全部写入数据库。然后我想可能在新线程上运行它会在需要编写日志时提高性能。以下是我的测试代码。如果我删除线程,我会在db中得到500行,但是当我使用多线程时,我会得到200-300左右。大约一半的记录没有被插入。这是多线程的有效用途,我做错了什么?谢谢。

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();
      }      
    }
  }

3 个解决方案

#1


1  

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. 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.

我看到的一些事情:首先,我不会使用List <>我会使用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:

此外,您应该在访问列表时锁定代码,如果在迭代时将项目添加到列表中,则可能会出现异常。这也适用于队列<>,我通常做的是:

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

Then also lock in your Add method (which you do).

然后还锁定你的Add方法(你这样做)。

#2


3  

ThreadStart thread = delegate { Flush(new List<LogEntry>(LogEntries)); };
      new Thread(thread).Start();
      //Flush(LogEntries);          
      LogEntries.Clear();

List<LogEntry> is a reference type. 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

List 是引用类型。您的新线程开始插入它们,但是在完成之前清除该列表。如果不使用多线程,则在清除之前等待刷新整个列表。您可以通过更改Flush的签名来修复此问题以获取数组并执行此操作

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

#3


1  

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.

在分析单行代码之前,Log Messages的中间存储位置错误。我强烈建议使用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. 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.

您在一个单独的线程中调用Flush并将引用传递给您的日志条目列表,然后清除当前线程中的列表。您已经有效地销毁了新线程应该记录的条目列表。在清除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.

顺便说一句,我会改变你的Flush方法来获取IEnumerable ,这样你就可以将其他集合而不仅仅是列表传递给方法。

#1


1  

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. 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.

我看到的一些事情:首先,我不会使用List <>我会使用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:

此外,您应该在访问列表时锁定代码,如果在迭代时将项目添加到列表中,则可能会出现异常。这也适用于队列<>,我通常做的是:

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

Then also lock in your Add method (which you do).

然后还锁定你的Add方法(你这样做)。

#2


3  

ThreadStart thread = delegate { Flush(new List<LogEntry>(LogEntries)); };
      new Thread(thread).Start();
      //Flush(LogEntries);          
      LogEntries.Clear();

List<LogEntry> is a reference type. 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

List 是引用类型。您的新线程开始插入它们,但是在完成之前清除该列表。如果不使用多线程,则在清除之前等待刷新整个列表。您可以通过更改Flush的签名来修复此问题以获取数组并执行此操作

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

#3


1  

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.

在分析单行代码之前,Log Messages的中间存储位置错误。我强烈建议使用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. 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.

您在一个单独的线程中调用Flush并将引用传递给您的日志条目列表,然后清除当前线程中的列表。您已经有效地销毁了新线程应该记录的条目列表。在清除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.

顺便说一句,我会改变你的Flush方法来获取IEnumerable ,这样你就可以将其他集合而不仅仅是列表传递给方法。