首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >BlockingCollection文件日志- BlockingCollection任务的线程自动中止

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

Stack Overflow用户
提问于 2018-04-06 08:32:18
回答 2查看 987关注 0票数 1

在每种情况下都是BlockingCollection线程安全的吗?

我试图使用BlockingCollection来实现日志系统,而不占用太多的主线程资源。这样做的想法是,主线程只需一个BlockingCollection.Add()调用,一个等待任务就可以完成编写工作。

BlockingCollection类应该负责处理与其队列的任务竞争,这样每个Add()都会连续地产生一个作业。

声明BlockingCollection对象:

代码语言:javascript
复制
private static BlockingCollection<string> queueLogMinimal = new BlockingCollection<string>();

和其他vars:

代码语言:javascript
复制
internal static string AllLogFileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "Log.log");
private static Object logLocker = new Object();

在构造函数:中启动将始终侦听的任务

代码语言:javascript
复制
    public LogManager()
    {
        Task.Factory.StartNew(() =>
        {
            Thread.CurrentThread.Name = "Logger";
            foreach (string message in queueLogMinimal.GetConsumingEnumerable())
            {
                WriteLogMinimal(message);
            }
        });
    }

:用于管理日志记录的最小函数:

代码语言:javascript
复制
    public static void LogMinimal(string message)
    {
        queueLogMinimal.Add(message);
    }

锁是必要的,以确保主线程不会在Logger写入时更改文件配置。

任务在每个BlockingCollection排线处调用的函数:

代码语言:javascript
复制
    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日志在一行

代码语言:javascript
复制
[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.BlockingCollection1.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“字符串

如果我在增加睡眠时间的同时不那么紧张地测试它:

代码语言:javascript
复制
[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());
    }
}

它记录了一切!(没有螺纹流产)。

是否有可能根据队列的速度对队列进行不同的管理,或者该速度可能“太快”?

EN

回答 2

Stack Overflow用户

发布于 2018-04-06 12:41:41

这里的问题是,在退出单元测试之前,您没有等待所有日志消息被处理。

Task.Factory.StartNew (顺便说一下,它不鼓励使用)在后台线程池线程上运行您的代码。当您的进程即将退出时-所有后台线程都被中止,这是您观察到的异常。

首先,在这样长时间运行的操作中使用线程池线程是不好的。最好为此启动新的后台线程,并保存在一个变量中,因为我们稍后将需要它:

代码语言:javascript
复制
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是合理的,但这并不是必要的,您只需要调用记录器通知它应该停止的一些方法。

代码语言:javascript
复制
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
    }
}

现在告诉您的记录器在退出之前停止:

代码语言:javascript
复制
[TestMethod]
public void TestLogs()
{
    LogManager lm = new LogManager();
    int a = 0;
    lm.LogMinimal(a++.ToString());
    // ...
    lm.Dispose();
}

最后注意:不要自己实现记录器,只需使用已经为您完成的现有解决方案,比如log4net。

票数 1
EN

Stack Overflow用户

发布于 2019-12-17 07:29:17

我觉得这里的情况和你想的不一样。

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

原因: Task.Factory.StartNew

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

不进行日志记录的情况是线程耗尽。请确认一下。

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

谢谢你的阅读

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/49688594

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档