关于多线程读写文件问题

Alaska_Lee 2015-06-30 10:33:45
我想写一个轻量级的日志工具,能满足多线程写的需求就行。
写了个单例的Logger类,通过lock来实现多线程的同步写文件。

public class Logger
{
private const int LogFileSizeLimit = 1024000;
private const string LogFileName = "log";
private static Logger instance;
private readonly string logDirectoryPath = Environment.CurrentDirectory +"\\log";
private readonly string logFile;

private Logger()
{
this.logFile = this,.logDirectoryPath+"\\"+logFileNmae+".txt";
}

private static readonly object O = new object();

public static Logger GetInstace()
{
lock(O)
{
return instance??(instance = new Logger());
}
}

private void ReadyToLog()
{
//文件的创建及验证功能。
}

private static object infoO = new object();
public void Info()
{
try
{
lock(infoO){ ReadyToLog();}
}
catch(Exceeption ex)
{
}
try
{
lock(infoO)
{
using(StreamWriter sw = File.AppendText(this.logFile))
{
sw.WriteLine(" sdfasdf ");
}
}
}
catch(exception ex)
{

}
}

}


然后写了测试方法

public void TestMethod2()
{
Lgger log = Logger.GetInstance();
for(int i = 0; i<20;i++)
{
Thread t = new Thread(Work);
t.Name = "t"+i;
t.Start(t.Name);
}
}

private void Work(object o)
{
string name = o.ToString();
int i=0;
while(i<1000)
{
Logger log = Logger.GetInstance();
Try{log.Info(name+" : 第"+i+"次获取实例并记录日志。")}
catch(exception ex){}
i++;
}
}

然后在测试运行的时候,偶尔会出现异常:threadAbortException 内容是代码已经过优化,或本机框架位于调用堆栈之上,无法计算表达式的值。异常大概出在lock(infoO)这句话上,不明白是什么原因引起的,求解。

也会有异常出现在using语句中,using语句不是用完了能释放资源的么,怎么会提示我所访问的文件正在被其他进程所使用。
...全文
357 17 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
17 条回复
切换为时间正序
请发表友善的回复…
发表回复
Alaska_Lee 2015-07-01
  • 打赏
  • 举报
回复
引用 12 楼 sp1234 的回复:
可以这样写一个简单的 Writelog 方法
private static object WriteLogFlag = new object();

public static void WriteLogToFileSystem(string className, object message, EventLogEntryType type)
{
    var content = GetEventString(message);  //无法确保对象引用message不被修改,因此此语据不能异步处理
    ThreadPool.QueueUserWorkItem(h =>
    {
        var dir = new DirectoryInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Log", DateTime.Now.ToString("yyyy_MM_dd")));
        var file = new FileInfo(Path.Combine(dir.FullName, className + "_" + type.ToString() + ".txt"));
        var sp = new string('-', 20);
        var msg = string.Format("{0} {1} {0}\r\n\r\n{2}\r\n\r\n", sp, DateTime.Now.ToString("HH:mm:ss.fff"), content);
        lock (WriteLogFlag)
        {
            CreateDir(dir);
            using (var fw = file.Open(FileMode.Append, FileAccess.Write))
            using (var sw = new StreamWriter(fw))
            {
                sw.Write(msg);
                sw.Close();
            }
        }
    });
}

private static string GetEventString(object message)
{
    string msg;
    if (message is Exception || message is string)
        msg = message.ToString();
    else
    {
        try
        {
            msg = JToken.FromObject(message).ToString();
        }
        catch
        {
            msg = message.ToString();
        }
    }
    return msg;
}
感觉这份代码和我的代码主要区别就是用线程池了,不知道会不会有效果。因为我那份代码找到原因了,所以你的这份代码还没有来得及测,准备明天测一下。还是谢谢你啦!
Alaska_Lee 2015-07-01
  • 打赏
  • 举报
回复
引用 6 楼 CGabriel 的回复:
[quote=引用 4 楼 lijie19871108 的回复:] 这样捕获不到异常,似乎是lock语句出现的异常,得在lock外层再加try.catch才能捕获到! 另外如果在lock块内中出现异常跳出,那么线程会释放锁吗?
1. 绝大多数情况之下,lock 语句不会出现异常 2. 要是真的有一场,可以产看异常堆栈,追踪到出错的地方 3. 在 lock 外面 try catch 是自己给自己制造麻烦 5. 锁会释放 最后给个建议,你去看 Log4Net 的源码吧,要不直接用 Log4Net,它已经很轻量级了。 你要依然嫌它笨重,可以砍掉它的代码,比自己搞省事多了[/quote] 出现这个异常的原因也找到了! 因为我是使用的log4net进行的单元测试,在单元测试中启动的20个线程还没执行完的时候,测试的主线程就已经退出了,导致了lock语句出现了异常。简单的说就是log4net不支持多线程测试吧。解决方法就是在TestMethod2()方法结束之前,让线程睡眠一段时间,或者在main()函数里测试。至于为什么主线程退出,子线程lock语句出现异常,还不明白为什么,可能需要看看源码。
Alaska_Lee 2015-07-01
  • 打赏
  • 举报
回复
引用 11 楼 caoqinghua 的回复:
[quote=引用 9 楼 lijie19871108 的回复:] [quote=引用 7 楼 caoqinghua 的回复:] 我认为你需要对文件共享状态读写 http://www.jb51.net/article/30552.htm
我通过同步锁的方式写,写完之后释放资源释放锁,为什么还要设置文件的共享状态呢?[/quote] 实验一下就知道结果了[/quote] 我没有试,但是我已经知道原因了! 因为我的代码除了写Info()函数外,还写了Exception()和error()函数,而exception()函数通过锁定exceptionO来控制多线程同步,error()函数通过锁定ErrorO来控制多线程同步,而每个函数都会写文件。在同一时刻,可能有3个线程分别在调用info()、exception()和error()来写文件。这就造成了写文件时,文件正在被其他进程占用。 设置文件共享状态,应该是一个能解决问题的方法,非常感谢! 我现在通过锁定同一个对象,来控制多个线程同步写文件。
  • 打赏
  • 举报
回复
或者调整那几行代码的位置
public static void WriteLogToFileSystem(string className, object message, EventLogEntryType type)
{
    var content = GetEventString(message);  //无法确保对象引用message不被修改,因此此语据不能异步处理
    var dir = new DirectoryInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Log", DateTime.Now.ToString("yyyy_MM_dd")));
    var file = new FileInfo(Path.Combine(dir.FullName, className + "_" + type.ToString() + ".txt"));
    var sp = new string('-', 20);
    var msg = string.Format("{0} {1} {0}\r\n\r\n{2}\r\n\r\n", sp, DateTime.Now.ToString("HH:mm:ss.fff"), content);
    ThreadPool.QueueUserWorkItem(h =>
    {
        lock (WriteLogFlag)
        {
            CreateDir(dir);
            using (var fw = file.Open(FileMode.Append, FileAccess.Write))
            using (var sw = new StreamWriter(fw))
            {
                sw.Write(msg);
                sw.Close();
            }
        }
    });
}
你可以用你的测试程序跑一下,看看有没有类似的异常现象。如果有就贴出测试程序来。
  • 打赏
  • 举报
回复
CreateDir(dir) 方法自己改写吧,就是创建一下目录而已。
  • 打赏
  • 举报
回复
可以这样写一个简单的 Writelog 方法
private static object WriteLogFlag = new object();

public static void WriteLogToFileSystem(string className, object message, EventLogEntryType type)
{
    var content = GetEventString(message);  //无法确保对象引用message不被修改,因此此语据不能异步处理
    ThreadPool.QueueUserWorkItem(h =>
    {
        var dir = new DirectoryInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Log", DateTime.Now.ToString("yyyy_MM_dd")));
        var file = new FileInfo(Path.Combine(dir.FullName, className + "_" + type.ToString() + ".txt"));
        var sp = new string('-', 20);
        var msg = string.Format("{0} {1} {0}\r\n\r\n{2}\r\n\r\n", sp, DateTime.Now.ToString("HH:mm:ss.fff"), content);
        lock (WriteLogFlag)
        {
            CreateDir(dir);
            using (var fw = file.Open(FileMode.Append, FileAccess.Write))
            using (var sw = new StreamWriter(fw))
            {
                sw.Write(msg);
                sw.Close();
            }
        }
    });
}

private static string GetEventString(object message)
{
    string msg;
    if (message is Exception || message is string)
        msg = message.ToString();
    else
    {
        try
        {
            msg = JToken.FromObject(message).ToString();
        }
        catch
        {
            msg = message.ToString();
        }
    }
    return msg;
}
caoqinghua 2015-07-01
  • 打赏
  • 举报
回复
引用 9 楼 lijie19871108 的回复:
[quote=引用 7 楼 caoqinghua 的回复:] 我认为你需要对文件共享状态读写 http://www.jb51.net/article/30552.htm
我通过同步锁的方式写,写完之后释放资源释放锁,为什么还要设置文件的共享状态呢?[/quote] 实验一下就知道结果了
CGabriel 2015-06-30
  • 打赏
  • 举报
回复

            lock(infoO)
            {
                try
                {
                    ReadyToLog();
                    File.AppendText(this.logFile, " sdfasdf ")
                }
                catch(Exceeption ex)
                {
                    // write error message to windows event log                
                }
            }
换掉这堆代码试试

        try
        {
            lock(infoO){ ReadyToLog();}
        }
        catch(Exceeption ex)
        {
        }
        try
        {
            lock(infoO)
            {
                using(StreamWriter sw = File.AppendText(this.logFile))
                {
                    sw.WriteLine(" sdfasdf ");
                }
            }
        }
        catch(exception ex)
        {
         
        }
Poopaye 2015-06-30
  • 打赏
  • 举报
回复
你自己调了Abort方法才会出现这个异常
Alaska_Lee 2015-06-30
  • 打赏
  • 举报
回复
引用 7 楼 caoqinghua 的回复:
我认为你需要对文件共享状态读写 http://www.jb51.net/article/30552.htm
我通过同步锁的方式写,写完之后释放资源释放锁,为什么还要设置文件的共享状态呢?
Alaska_Lee 2015-06-30
  • 打赏
  • 举报
回复
引用 6 楼 CGabriel 的回复:
[quote=引用 4 楼 lijie19871108 的回复:] 这样捕获不到异常,似乎是lock语句出现的异常,得在lock外层再加try.catch才能捕获到! 另外如果在lock块内中出现异常跳出,那么线程会释放锁吗?
1. 绝大多数情况之下,lock 语句不会出现异常 2. 要是真的有一场,可以产看异常堆栈,追踪到出错的地方 3. 在 lock 外面 try catch 是自己给自己制造麻烦 5. 锁会释放 最后给个建议,你去看 Log4Net 的源码吧,要不直接用 Log4Net,它已经很轻量级了。 你要依然嫌它笨重,可以砍掉它的代码,比自己搞省事多了[/quote] lock异常追踪只能到 在system.threading.monitor.reliableEnter(Object obj, boolean &lockTaken) 在system.threading.monitor.Enter(object onj, boolean &lockTaken) 另外在lock外面加try catch为什么不妥?
caoqinghua 2015-06-30
  • 打赏
  • 举报
回复
我认为你需要对文件共享状态读写 http://www.jb51.net/article/30552.htm
CGabriel 2015-06-30
  • 打赏
  • 举报
回复
引用 4 楼 lijie19871108 的回复:
这样捕获不到异常,似乎是lock语句出现的异常,得在lock外层再加try.catch才能捕获到! 另外如果在lock块内中出现异常跳出,那么线程会释放锁吗?
1. 绝大多数情况之下,lock 语句不会出现异常 2. 要是真的有一场,可以产看异常堆栈,追踪到出错的地方 3. 在 lock 外面 try catch 是自己给自己制造麻烦 5. 锁会释放 最后给个建议,你去看 Log4Net 的源码吧,要不直接用 Log4Net,它已经很轻量级了。 你要依然嫌它笨重,可以砍掉它的代码,比自己搞省事多了
Alaska_Lee 2015-06-30
  • 打赏
  • 举报
回复
引用 1 楼 shingoscar 的回复:
你自己调了Abort方法才会出现这个异常
在lock的时候出现了这个异常: mscorlib - void ReliableEnter(system.object, boolean byref):system.threading.ThreadAbortException 在system.threading.monitor.reliableEnter(Object obj, boolean &lockTaken) 在system.threading.monitor.Enter(object onj, boolean &lockTaken) 在LogManager.Logger.Info(Object obj) 行号130就是lock(infoO)这一行。
Alaska_Lee 2015-06-30
  • 打赏
  • 举报
回复
引用 2楼CGabriel 的回复:

            lock(infoO)
            {
                try
                {
                    ReadyToLog();
                    File.AppendText(this.logFile, " sdfasdf ")
                }
                catch(Exceeption ex)
                {
                    // write error message to windows event log                
                }
            }
换掉这堆代码试试

        try
        {
            lock(infoO){ ReadyToLog();}
        }
        catch(Exceeption ex)
        {
        }
        try
        {
            lock(infoO)
            {
                using(StreamWriter sw = File.AppendText(this.logFile))
                {
                    sw.WriteLine(" sdfasdf ");
                }
            }
        }
        catch(exception ex)
        {
         
        }
这样捕获不到异常,似乎是lock语句出现的异常,得在lock外层再加try.catch才能捕获到! 另外如果在lock块内中出现异常跳出,那么线程会释放锁吗?
Alaska_Lee 2015-06-30
  • 打赏
  • 举报
回复
引用 1楼shingoscar 的回复:
你自己调了Abort方法才会出现这个异常
代码里并没有显式调用abort方法!

111,092

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • AIGC Browser
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧