BlockingCollection文件日志记录 - BlockingCollection任务的线程自动中止

时间:2018-04-06 08:32:18

标签: c# multithreading logging task blockingcollection

在任何情况下BlockingCollection线程都是安全的吗?

我正在尝试使用BlockingCollection来实现日志系统,而不需要占用太多的主线程资源。这个想法是它只花费主线程的BlockingCollection.Add()调用,并且一个等待任务执行写作。

BlockingCollection类应该用它的队列来处理任务竞争,这样每个Add()都会连续带来一个工作。

声明BlockingCollection对象:

private static BlockingCollection<string> queueLogMinimal = new BlockingCollection<string>();

其他变种:

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()中,在foreach中使用GetConsumingEnumerable()浏览BlockingCollection元素:

  

线程正在中止。

StackTrace:

  

at System.Threading.Monitor.ObjWait(布尔值exitContext,Int32毫秒时间,对象obj)\ r \ n

   at System.Threading.Monitor.Wait(Object obj,Int32 millisecondsTimeout,Boolean exitContext)\ r \ n

  at System.Threading.SemaphoreSlim.WaitUntilCountOrTimeout(Int32 millisecondsTimeout,UInt32 startTime,CancellationToken cancellationToken)\ r \ n at System.Threading.SemaphoreSlim.Wait(Int32 millisecondsTimeout,CancellationToken cancellationToken)\ r \ n
   at System.Collections.Concurrent.BlockingCollection`1.TryTakeWithNoTimeValidation(T&amp; item,Int32 millisecondsTimeout,CancellationToken cancellationToken,CancellationTokenSource combinedTokenSource)\ r \ n

   at System.Collections.Concurrent.BlockingCollection`1.d__68.MoveNext()\ r \ n

  在RenaultTrucks.Common.Managers.LogManager。&lt;&gt; c。&lt; .ctor&gt; b__14_1()    C:\ Source \ Diagnostica \ GLB-DIAGNOSTICA \ NewDiagnostica \ Source \ Common \ Managers \ LogManager.cs:第61行&#34;串

如果我在增加睡眠时间的同时进行较少的测试:

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

记录一切! (没有线程流产)。

是否可能根据排队速度对队列进行不同的管理,或者该速度可能过快&#34;吗

2 个答案:

答案 0 :(得分:1)

问题在于,您不会在退出单元测试之前等待处理所有日志消息。

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。

答案 1 :(得分:0)

我认为这里的图片与您认为的没什么不同。

基本上,您的线程不在等待日志记录,请注意线程数将根据您的情况不断增加。

原因:Task.Factory.StartNew

这使您所有的新线程都可以处理blockingcollection.GetConsumingEnumerables。.

您未记录日志的情况是线程耗尽。请验证。

建议不要使用StartNew,而是使用线程类并运行它。

感谢阅读