我有一个Windows服务项目,它将消息记录到数据库(或其他地方)。这些消息的频率最高可达每秒十个。由于发送和处理消息不应该延迟服务的主进程,因此我开始一个新线程来处理每个消息。这意味着如果主进程需要发送100条日志消息,则会启动100个处理每条消息的线程。我了解到,当一个线程完成后,它将被清理,所以我不必处理它。只要我在线程中处理所有使用过的对象,一切都应该正常工作。
该服务可能会进入导致关闭服务的异常。在服务关闭之前,它应该等待所有记录消息的线程。为实现此目的,每次启动线程时,它都会将线程添加到列表中。调用wait-for-threads方法时,将检查列表中的所有线程是否仍处于活动状态,如果是,则使用join等待它。
代码:
创建线程:
/// <summary>
/// Creates a new thread and sends the message
/// </summary>
/// <param name="logMessage"></param>
private static void ThreadSend(IMessage logMessage)
{
ParameterizedThreadStart threadStart = new ParameterizedThreadStart(MessageHandler.HandleMessage);
Thread messageThread = new Thread(threadStart);
messageThread.Name = "LogMessageThread";
messageThread.Start(logMessage);
threads.Add(messageThread);
}
等待线程结束:
/// <summary>
/// Waits for threads that are still being processed
/// </summary>
public static void WaitForThreads()
{
int i = 0;
foreach (Thread thread in threads)
{
i++;
if (thread.IsAlive)
{
Debug.Print("waiting for {0} - {1} to end...", thread.Name, i);
thread.Join();
}
}
}
现在我主要担心的是,如果这个服务运行一个月,它仍将拥有列表中的所有线程(数百万)(大多数已经死亡)。这会吃掉记忆,我不知道多少。这对我来说似乎不是一个好习惯,我想清理已完成的线程,但我无法找到如何做到这一点。有没有人有这个好的或最好的做法?
答案 0 :(得分:2)
如果它们已经死了,请从列表中删除它们吗?
/// <summary>
/// Waits for threads that are still being processed
/// </summary>
public static void WaitForThreads()
{
List<Thread> toRemove = new List<int>();
int i = 0;
foreach (Thread thread in threads)
{
i++;
if (thread.IsAlive)
{
Debug.Print("waiting for {0} - {1} to end...", thread.Name, i);
thread.Join();
}
else
{
toRemove.Add(thread);
}
}
threads.RemoveAll(x => toRemove.Contains(x));
}
答案 1 :(得分:2)
首先:每条日志消息创建一个线程不是个好主意。使用ThreadPool
或创建有限数量的工作线程来处理来自公共队列(生产者/消费者)的日志项。
第二:当然你还需要从列表中删除线程引用!当线程方法结束时,它可以自行删除,或者你甚至可以定期执行它。例如,让计时器每半小时运行一次,检查列表中的死线程并将其删除。
答案 2 :(得分:1)
如果你在这些线程中所做的只是记录,你应该有一个记录线程和主线程放置消息的共享队列。然后,日志记录线程可以读取队列并记录日志。使用BlockingCollection非常容易。
在服务的主线程中创建队列:
BlockingCollection<IMessage> LogMessageQueue = new BlockingCollection<IMessage>();
您的服务的主线程创建一个Logger
(见下文)实例,该实例启动一个线程来处理日志消息。主线程将项添加到LogMessageQueue
。记录器线程从队列中读取它们。当主线程想要关闭时,它会调用LogMessageQueue.CompleteAdding
。记录器将清空队列并退出。
主线程看起来像这样:
// start the logger
Logger _loggingThread = new Logger(LogMessageQueue);
// to log a message:
LogMessageQueue.Add(logMessage);
// when the program needs to shut down:
LogMessageQueue.CompleteAdding();
记录器类:
class Logger
{
BlockingCollection<IMessage> _queue;
Thread _loggingThread;
public Logger(BlockingCollection<IMessage> queue)
{
_queue = queue;
_loggingThread = new Thread(LoggingThreadProc);
}
private void LoggingThreadProc(object state)
{
IMessage msg;
while (_queue.TryTake(out msg, TimeSpan.Infinite))
{
// log the item
}
}
}
这样你只有一个额外的线程,保证消息按照它们发送的顺序进行处理(不适用于你当前的方法),你不必担心跟踪线程关闭等
如果您的某些日志消息需要一些时间来处理(例如,您所描述的电子邮件),则可以异步处理它们。例如:
while (_queue.TryTake(out msg, TimeSpan.Infinite))
{
if (msg.Type == Email)
{
// start asynchronous task to send email
}
else
{
// write to log file
}
}
这样,只有那些可能需要大量时间的消息才会异步运行。如果需要,您还可以在其中为电子邮件设置辅助队列。这样你就不会陷入一堆电子邮件线程。相反,你把它限制在一两个,或者可能是极少数。
请注意,如果需要,您还可以拥有多个Logger
个实例,所有实例都从同一个消息队列中读取。只需确保他们每个人都写入不同的日志文件。队列本身将支持多个消费者。
答案 3 :(得分:0)
我认为一般来说,解决问题的方法可能不是最佳做法。 我的意思是,你只想在数据库中存储1000条消息,而不是创建1000个线程吗?似乎你想要异步地做这件事。
但是为每条消息创建一个线程并不是一个好主意,实际上并没有解决这个问题......
相反,我会尝试实现消息队列之类的东西。您可以拥有多个队列,每个队列都有自己的线程。如果消息进入,您将它们发送到其中一个队列(交替)...
队列要么等待一定数量的消息,要么总是等待一定的时间(例如1秒,取决于在数据库中存储例如100条消息所需的时间),直到它尝试存储排队的消息为止在数据库中。 这样你实际上应该总是有一个恒定数量的线程,你不应该看到任何性能问题......
此外,它还可以让您批量插入数据,而不仅仅是逐个数据库连接等的开销......
原因是,如果您的数据库速度较慢,则任务可以存储消息,则会有越来越多的消息排队......但对于您当前的解决方案来说也是如此。
答案 4 :(得分:0)
由于我的解决方案有多个答案和评论,我将在此处发布完整的代码。
我使用threadpool来管理来自这个page的线程和代码,用于wating函数。
创建线程:
private static void ThreadSend(IMessage logMessage)
{
ThreadPool.QueueUserWorkItem(MessageHandler.HandleMessage, logMessage);
}
等待线程完成:
public static bool WaitForThreads(int maxWaitingTime)
{
int maxThreads = 0;
int placeHolder = 0;
int availableThreads = 0;
while (maxWaitingTime > 0)
{
System.Threading.ThreadPool.GetMaxThreads(out maxThreads, out placeHolder);
System.Threading.ThreadPool.GetAvailableThreads(out availableThreads, out placeHolder);
//Stop if all threads are available
if (availableThreads == maxThreads)
{
return true;
}
System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(1000));
--maxWaitingTime;
}
return false;
}
您可以选择在这些方法之外的某处添加它以限制池中的线程数量。
System.Threading.ThreadPool.SetMaxThreads(MaxWorkerThreads, MaxCompletionPortThreads);