C#基于日志功能的队列讲解(二)程序讲解
一、LogServer程序部分讲解
1.LogServer程序代码
public class LogServer
{
private ConcurrentQueue<LogInfo> logQueue = new ConcurrentQueue<LogInfo>();
private Task task;
public static LogServer instance { get; private set; } = new LogServer();
private LogServer()
{
task = Task.Run(() => doWork());
//循环等待任务启动,不启动这里是不是一直阻塞
while (true)
{
if(task.Status==TaskStatus.Running)
{
break;
}
Thread.Sleep(200);
}
}
private void doWork()
{
while (true)
{
int faiCount = 0;
if (logQueue.Count > 0)
{ //失败次数
//如果队列元素大于0,取出数组
//获取一个日志 取出来数据
logQueue.TryDequeue(out LogInfo logInfo);
Debug.WriteLine(logInfo.line);
LogStart: try
{ //using 代码块 创建对象 创建对象完成后自动调用Dispose方法
//创建流对象 logInfo。path,地址,true,Encoding.UTF8
using (StreamWriter sw = new StreamWriter(logInfo.path, true, Encoding.UTF8))
{
sw.WriteLine(logInfo.line);
}
}
catch (Exception)
{
faiCount++;
if (faiCount < 20)
{
Thread.Sleep(100);
goto LogStart;
}
else
{ //提醒用户写入失败,弹窗也可以,三色灯也可以
Debug.WriteLine("写入日志失败");
}
}
}
}
}
private string logPath = @"D:\log\";
public void writeLog(string line)
{ //创建日志文件包含路径和文件名
string FileName = $"{logPath}{DateTime.Now:yyyy-MM}\\{DateTime.Now:yyyy-MM-dd}_LOGINFOR.txt";
Debug.WriteLine(FileName);
//创建目录
if (!Directory.Exists(FileName.Substring(0, FileName.LastIndexOf("\\"))))
{
Directory.CreateDirectory(FileName.Substring(0, FileName.LastIndexOf("\\")));
}
line = $"{DateTime.Now:HH:mm:SS:fff}=>{line}";
writeline(FileName, line);
}
public object obj = new object();
private void writeline(string fileName, string line)
{
lock (obj)
{
LogInfo logInfo = new LogInfo();
logInfo.path = fileName;
logInfo.line = line;
logQueue.Enqueue(logInfo);
}
}
}
}
2.LogServer程序讲解
1.队列类型(ConcurrentQueue<T>)

首先C#的队列的建立是通过ConcurrentQueue<T>进行创建,首先什么是队列,其实对列很简单,就是FIFO先进先出。我们可以看下labview实现队列的方式
首先labview队列完整实现需要的函数是获取队列-入队列-出队列-结束队列

首先看下Labview的队列引用 ,引用中包含队列名,元素的数据类型,这个是队列最重要两个参数

我们看一下C#程序中队列是怎么引用的,首先是ConcurrentQueue(线程安全队列),他的本质是什么,C#有描述,因为它后面带了T,所以肯定是泛型而且是一个特殊的泛型集合。
C#还有一个Queue(非安全型队列)这个在多线程引用会出现崩溃的问题,多次Thread,会有问题。程序解读:就是实例化一个名称为logQueue的一个队列,队列元素是LogInfo(类),等同于Labview的获取队列引用


1.2队列的基本格式
private ConcurrentQueue<你要存的类型> 队列名称 = new ConcurrentQueue<你要存的类型>();
2.单例模式(instance)
我们为什么建立队列后采用单例模式,其实与Labview的理念是相同的,在labview使用队列的时候,普遍都是在多个VI进行调用队列,往同一个队列状态机里传递队列元素 ,不可能是多个VI调用队列 ,往多个相同队列状态机里传递,那样传递,程序逻辑可以不用玩了。所以C#在使用队列的时候其实也是这样的一个设计思路,所以C#在使用队列的时候,必须创建一个单例模式,禁止外部创建新对象 ,保证程序里只有LogServer一个对象,也就永远只有一个 logQueue 队列,所有调用的都往这一个队列里塞。

分析LogServer ,其实跟Labview的解释是一样的,我们先看下我们正常编写Labview的时候,我们也是一直通过While循环,等待出队列,等待时间为100,也就是意味着每隔100毫秒检查队列元素,并输出。

C#中创建了一个task,Task.Run((()=>doWork())这个意思是从异步调用线程池中取出一个线程去执行doWork方法并赋值给task这个变量实例,它的作用是在不影响主线程的情况下,异步执行一个线程,相当于我们Labview创建了一个需要执行队列状态机的VI,VI名称时doWork,并且我们触发这个Vi是通过异步调用实现的。
我们讲一下C#中Whie循环中的意义,就是当我们把线程分配给doWork后,进入While循环,循环结束条件设置为true,在While循环中判断如果doWork的状态时执行了,那么通过Break实现Whie循环结束。
它这个的作用相当与Labview的对VI在调用时,去判断调用vi时的一个状态,如果doWork已经运行了,那么就退出While,反之,等待200ms继续检查线程有没有执行。


2.1doWork
在DoWork方法中,While循环,在循环中,我们通过if判断,判断.logQueue.Count(队列元素个数)如果大于0,将元素取出并Out输出参数logInfo。logQueue.tryDequeue等同于出队列。


2.2SteamWriter
StreamWriter 是 C# 里专门用来写文本文件的类,会自动处理编码、换行等细节,与labview的文本写入相类似,SteamWriter跟labview一样,也是需要一些设置参数,
logInfo.path=文件路径
true=代表内容是不是覆盖还是追加模式写入下一行;与labview写入带分隔符电子字符表格函数中添加至文件?的设置是相同的
Encding.UTF8,=文本格式,这个格式跟labview是不一样的代表的是文本格式,是ASCII,还是其他因为文本内容有中文所以这里用的是UTF8,如果只有英文就可以用ASCII,labview的格式是输入参数的类型,二者不一样
然后WriteLine写入内容



2.3.Try..catch
C#的Try..catch与labview对错误簇的处理机制是相同的,我们读一下Try..catch C#的代码内容先执行文本写入,如果文本写入不成功,那么进入catch,先判断一下写入失败次数是不是小于20,如果小于20,那么等待100毫秒,再尝试执行文本写入 ,如果已经写入失败次数超过20,那么就结束。

2.4.单例模式格式
public class 类名
{
// 1. 唯一的静态实例(全局只有这一个对象)
public static 类名 Instance { get; } = new 类名();
// 2. 私有构造(禁止外部 new 对象)
private 类名()
{
}
2.5WriteLog方法讲解
建立绝对路径D:\log\
![]()
创建文本文件夹名称,文本文件名称
![]()
这个相当于Labview检查文件是否存在,如果文件不存在,创建文件

Directory.Exists作用就是检查是否存在,与labview检查文件/文件夹是否存在一致
Directory.CreateDurector作用就是创建目录,跟labview的创建文件夹及文件函数一致



Substring有点相当于匹配模式,因为这个labview的字符串的函数其实我感觉用起来,有好几个函数功能上带有点多功能的效果,C#字符串语句其实分的比较细,在语法基础使用上C#和labview还有非常大的区别。在程序中创建文本的条件就是检查是否搜索匹配到路径"\\",如果没有,就创建

将写入数据传递到writeline方法里

2.6 writeline方法

writeline的作用其实就是将路径和内容传递给logInfo,首先将LogInfo实例化,然后将获取的参数进行赋值,将含有path和line参数的logInfo进入队列,我们之前代码出队列变量是logInfo,所以入队列的时候变量也是logInfo,然后在出队列
2.7logQueue.Enqueue和Lock
入队列,这个就是Labview的入队列,将logInfo入队列,但是C#的入队列跟labview的入队列有很大的不同,labview入队列的时候,不管多少线程同时往里扔数据,它自己内部会排队、上锁、保护,C#没有这个机制,如果很多地方都在调用,队列内部结构会被破坏,数据乱掉甚至崩溃,所以它要加一个lock,上锁机制,lock 的作用 = 排队执行,同一时间只允许一个人操作队列。
2.8Lock基础格式
1.定义一个锁对象,就是相当家里要挂一个锁头,啥牌子的呢,就是logLock这个牌子的锁
// 定义一个锁对象(锁队列用)
private readonly object logLock = new object();
2.上锁格式,lock 的作用 = 排队执行,同一时间只允许一个人操作队列。
// 上锁 → 同一时间只能有一个线程执行
lock (logLock)
{
logQueue.Enqueue(logInfo);
}
3.LogInfo程序讲解
编写这个程序类的目的是为了WPF前面板Btn触发后,有方法将路径和内容传递给LogServer中的writeLog

4.程序运行逻辑
前面板btn按下后,触发LogServer单例,并将写入内容,通过writelog方法传递进去

立马进入方法writelog,然后开始进行文本检查,创建文本,然后入队列

出队列,写入文本
总结
其实我们在编写程序的时候不管用什么语言,逻辑思路其实大体都是一样的,labview实现文本的写入也是这套逻辑,只不过展现方式不一样。语言只是代码逻辑表达展示的一个方式,没有谁优谁差。labview想对高度集成了一些,就像乐高积木一样,模块化的比较多,C#更像一个碎片,更锻炼程序员的一个思维能了,需要把碎片语言粘合成一个方法,在组成类,然后再拿去使用。所以C#的学习周期反而更长,对程序员的语言基础要求非常高。语言没有好与坏,学精了都不易。
我做了8年的labview,用labview也曾独立带过大型项目,直到现在我也不敢说我把labview用的有多精,什么问题都能解决。学东西越往深了学,越会发现,自己其实还有好多不明白的地方,就像数学一样,越往深了学,越会发现自己好多东西都学不明白,甚至已经超出自己能理解的思维范畴了。语言也是一样,我只能说,够用,能应付现在的工作,但绝不敢说自己有多牛。
学无止境,与君共勉
更多推荐

所有评论(0)