2011-03-29 123 views
0

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

日志管理:

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

控制台应用程序:

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

我的第一个猜测是,当循环停止时,你有许多日志条目还没有被刷新。 – RQDQ 2011-03-29 14:44:16

+0

请勿使用{{throw(ex);/* KeepGoing * /}。它弄乱了你的堆栈,你仍然需要处理异常。 – 2011-03-29 14:44:28

+0

我遗漏了(前)错误。我希望它继续下去,如果它打嗝。你是说我应该删除所有的尝试? – Mike 2011-03-29 14:48:44

回答

1

一对夫妇的事情,我认为解决这个问题:首先,我不会用一个List<>我会用一个Queue<>,它更适合这个情况。其次,在你开启线程后,清除列表。所以在线程真正开始执行代码的时候,这个列表已经是空的了。 A Queue<>应该有助于解决此问题,因为您可以在队列写入数据库时​​从队列中删除项目。

另外,当您访问列表时,您应该锁定您的代码,如果在迭代它时将某项添加到列表中,您可能会遇到异常。这将适用于Queue<>还有,我通常做的是一样的东西:

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

然后也锁定在Add方法(你这样做)。

1

之前我分析你的代码的单个行,你的日志消息的中介存储是在错误的地方。强烈建议在等待LogManager进行处理时使用MSMQ或其他排队机制来存储消息。

您在单独的线程中调用Flush并将引用传递给您的日志条目列表,然后清除当前线程中的列表。您已经有效地销毁了新线程应该记录的条目列表。在清除LogEntries字段之前,您需要将LogEntries列表的副本传递到Flush线程。

也许是这样的:

{Flush(LogEntries.ToList())} 

的LINQ表达ToList()将创建列表的副本为你冲洗方法。另外,我会改变你的Flush方法来采用IEnumerable<LogEntry>,这样你就可以将其他集合,而不仅仅是列表传递给方法。

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

List<LogEntry>是参考类型。您的新线程开始插入它们,但是在完成之前清除该列表。当你不使用多线程时,你需要等待整个列表被刷新,然后清除它。您可以通过改变Flush签名拿一个数组,做

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