在每种情况下都是BlockingCollection线程安全的吗?
我试图使用BlockingCollection来实现日志系统,而不占用太多的主线程资源。这样做的想法是,主线程只需一个BlockingCollection.Add()调用,一个等待任务就可以完成编写工作。
BlockingCollection类应该负责处理与其队列的任务竞争,这样每个Add()都会连续地产生一个作业。
声明BlockingCollection对象:
private static BlockingCollection<string> queueLogMinimal = new BlockingCollection<string>();和其他vars:
internal static string AllLogFileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "Log.log");
private static Object logLocker = new Object();在构造函数:中启动将始终侦听的任务
public LogManager()
{
Task.Factory.StartNew(() =>
{
Thread.CurrentThread.Name = "Logger";
foreach (string message in queueLogMinimal.GetConsumingEnumerable())
{
WriteLogMinimal(message);
}
});
}:用于管理日志记录的最小函数:
public static void LogMinimal(string message)
{
queueLogMinimal.Add(message);
}锁是必要的,以确保主线程不会在Logger写入时更改文件配置。
任务在每个BlockingCollection排线处调用的函数:
public static void WriteLogMinimal(string message)
{
lock (logLocker)
{
try
{
File.AppendAllText(AllLogFileName, DateTime.Now.ToString() + " - " +message +Environment.NewLine);
}
catch (Exception e)
{
Debug.WriteLine("EXCEPTION while loging the message : ");
}
}
}问题是,当测试该系统的功能时,要做一行日志。如果我测试,假设40日志在一行中
[TestClass]
public class UnitTest
{
[TestMethod]
public void TestLogs()
{
LogManager lm = new LogManager();
int a = 0;
LogManager.LogMinimal(a++.ToString());
LogManager.LogMinimal(a++.ToString());
LogManager.LogMinimal(a++.ToString());
LogManager.LogMinimal(a++.ToString());
}
}在10到30条日志之后,它会停止日志记录,因为这发生在LaunchLogTask()中,在使用GetConsumingEnumerable()浏览BlockingCollection元素的过程中:
线程被中止了。
StackTrace :
at System.Threading.Monitor.ObjWait(布尔值exitContext,Int32 millisecondsTimeout,Object obj)\r\n at System.Threading.Monitor.Wait(Object obj,Int32 millisecondsTimeout,Boolean )\r\n 在System.Threading.SemaphoreSlim.WaitUntilCountOrTimeout(Int32 millisecondsTimeout,UInt32 startTime,CancellationToken cancellationToken)\r\n at System.Threading.SemaphoreSlim.Wait(Int32 millisecondsTimeout,CancellationToken cancellationToken)\r\n System.Collections.Concurrent.BlockingCollection
1.TryTakeWithNoTimeValidation(T& item, Int32 millisecondsTimeout, CancellationToken cancellationToken, CancellationTokenSource combinedTokenSource)\r\n **at** System.Collections.Concurrent.BlockingCollection1.d__68.MoveNext()\r\n at at RenaultTrucks.Common.Managers.LogManager.<>c.<.ctor>b__14_1() in C:\Source\Diagnostica\GLB-DIAGNOSTICA\NewDiagnostica\Source\Common\Managers\LogManager.cs:line 61“字符串
如果我在增加睡眠时间的同时不那么紧张地测试它:
[TestClass]
public class UnitTest
{
[TestMethod]
public void TestLogs()
{
LogManager lm = new LogManager();
int a = 0;
LogManager.LogMinimal(a++.ToString());
System.Threading.Thread.Sleep(100);
LogManager.LogMinimal(a++.ToString());
System.Threading.Thread.Sleep(100);
LogManager.LogMinimal(a++.ToString());
System.Threading.Thread.Sleep(100);
LogManager.LogMinimal(a++.ToString());
}
}它记录了一切!(没有螺纹流产)。
是否有可能根据队列的速度对队列进行不同的管理,或者该速度可能“太快”?
发布于 2018-04-06 12:41:41
这里的问题是,在退出单元测试之前,您没有等待所有日志消息被处理。
Task.Factory.StartNew (顺便说一下,它不鼓励使用)在后台线程池线程上运行您的代码。当您的进程即将退出时-所有后台线程都被中止,这是您观察到的异常。
首先,在这样长时间运行的操作中使用线程池线程是不好的。最好为此启动新的后台线程,并保存在一个变量中,因为我们稍后将需要它:
private readonly Thread _consumerThread;
public LogManager() {
new Thread(() => {
Thread.CurrentThread.Name = "Logger";
try {
foreach (string message in queueLogMinimal.GetConsumingEnumerable()) {
try {
LogMinimal(message);
}
catch (Exception ex) {
// do something, otherwise one failure to write a log
// will bring down your whole logging
}
}
}
catch (Exception ex) {
// do something, don't let exceptions go unnoticed
}
}) {
IsBackground = true
}.Start();
}现在,在退出进程之前,您需要让所有挂起的消息被处理。实现IDisposable是合理的,但这并不是必要的,您只需要调用记录器通知它应该停止的一些方法。
public void Dispose() {
// first notify that no messages are expected any more
// this will allow GetConsumerEnumerable to finish
this.queueLogMinimal.CompleteAdding();
// now wait for thread to finish with reasonable timeout
// don't do this without timeout to prevent potential deadlocks
bool finishedGracefully = _consumerThread.Join(TimeSpan.FromSeconds(5));
if (!finishedGracefully) {
// thread did not finish during timeout,
// do something like throwing exception
}
}现在告诉您的记录器在退出之前停止:
[TestMethod]
public void TestLogs()
{
LogManager lm = new LogManager();
int a = 0;
lm.LogMinimal(a++.ToString());
// ...
lm.Dispose();
}最后注意:不要自己实现记录器,只需使用已经为您完成的现有解决方案,比如log4net。
发布于 2019-12-17 07:29:17
我觉得这里的情况和你想的不一样。
基本上,您的线程没有等待日志记录,请注意,在您的情况下,线程计数将不断增加。
原因: Task.Factory.StartNew
这将使您所有的新线程都能处理blockingcollection.GetConsumingEnumerables。
不进行日志记录的情况是线程耗尽。请确认一下。
按照建议,不要使用StartNew,而是使用线程类并运行它。
谢谢你的阅读
https://stackoverflow.com/questions/49688594
复制相似问题