这是我进入多线程的第一次冒险,我想我缺少一些关键概念,所以任何帮助都将受到赞赏。我正在尝试为asp.net应用程序创建一个日志管理器。我们将在系统中的大量数据上记录视图/插入/修改/删除。而不是不断地插入行,我想如果我创建一个单例来保存内存中的日志条目列表,直到它达到一定大小,然后立即将它们全部写入数据库。然后我想可能在新线程上运行它会在需要编写日志时提高性能。下面是我的测试代码。如果我删除线程,我会在db中得到500行,但是当我使用多线程时,我会得到200-300左右。大约一半的记录没有被插入。这是多线程的有效用途,我做错了什么?谢谢。
日志管理:
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 :(得分: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();
答案 1 :(得分:1)
在分析单行代码之前,Log Messages的中间存储位置错误。我强烈建议在等待LogManager处理时使用MSMQ或其他一些排队机制存储您的消息。
您在一个单独的线程中调用Flush并将引用传递给您的日志条目列表,然后清除当前线程中的列表。您已经有效地销毁了新线程应该记录的条目列表。在清除LogEntries
字段之前,您需要将LogEntries
列表的副本传递到Flush主题。
也许是这样的:
{Flush(LogEntries.ToList())}
LINQ表达式ToList()
将为您的Flush方法创建一个列表副本。
顺便说一句,我会改变您的Flush方法以获取IEnumerable<LogEntry>
,以便您可以将其他集合(而不仅仅是列表)传递给方法。
答案 2 :(得分:1)
我看到的一些事情:首先,我不会使用List<>
我会使用Queue<>
,它更适合这种情况。其次,在您关闭线程后立即清除列表。因此,当线程实际开始执行其代码时,很有可能列表已经为空。 Queue<>
应该有助于解决此问题,因为您可以在将数据写入数据库时从队列中删除项目。
此外,您应该在访问列表时锁定代码,如果在迭代时将项目添加到列表中,则可能会出现异常。这也适用于Queue<>
,我通常做的是:
LogEntry myEntry;
lock(sync) {
myEntry = myQueue.Dequeue();
}
然后还锁定您的Add
方法(您这样做)。