2011-03-29 121 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(); 
相關問題