C# 控制文本并发访问
在我们的WebService中有下面一个方法,用于在service访问中如果发生异常时,记录此异常所用!
由于有很多系统都在调用此web service,也就是说对此log.txt的访问实际为并发操作。这也就存在一个并发控制的问题:
{
Monitor.Enter(lockObject);
System.IO.StreamWriter sw = null;
try
{
string logFile = Server.MapPath("~/Log/" + this.GetType().Name + ".txt");
string fullText = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + "\t" + HttpContext.Current.Request.UserHostAddress + "\t" + detailDesc;
checkFile(logFile);
sw = System.IO.File.AppendText(logFile);
sw.WriteLine(fullText);
sw.Flush();
}
finally
{
Monitor.Exit(lockObject);
sw.Close();
}
}
在多线程的环境中为了避免同一时间有多线程同时执行写档的程序而造成例外状况。
理论上看似不会出问题的程序跑到catch的区段?错误信息如下:
The process cannot access the file 'D:\log_20100630.txt' because it is being used by another process.
到底出了什么问题?另外msdn中查到
請使用 Enter,來取得當做參數傳遞之物件上的 Monitor。如果有其他執行緒已在物件上執行過 Enter,但還沒有執行對應的 Exit,目前的執行緒會阻斷,直到另一執行緒釋出物件為止。同一個執行緒叫用 Enter 不只一次而不發生封鎖是合法的情形;不過,必須先叫用等數量的 Exit 呼叫,等候物件的其他執行緒才會解除封鎖。
问题很简单,sw.Close()放在了 Monitor.Exit(lockObject);之后。我们知道 Monitor.Exit(lockObject)用于unlock锁定的对象,使得程序可被进入。而sw.Close()用于释放sw资源,关闭txt文本,使得下一个process可以打开并访问。而如果按照现在的执行逻辑就可能造成:unlock对象后,在释放sw资源资源之前就有另一个process进入访问,必然出错。故而应先释放资源,而后Unlock才可保证并发操作正常进行。
而实际上我们知道,对于sw这类非托管资源,他们都实现了IDisposable 接口,我们可以用更简单的方法来控制资源的释放:那就是利用Using关键字。
{
Monitor.Enter(lockObject);
try
{
string logFile = HttpContext.Current.Server.MapPath("~/Log/" + this.GetType().Name + "_" + DateTime.Now.ToString("yyyyMMdd") + ".txt");
string fullText = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + "\t" + HttpContext.Current.Request.UserHostAddress + "\t" + detailDesc;
checkFile(logFile);
using (System.IO.StreamWriter sw = System.IO.File.AppendText(logFile))
{
sw.WriteLine(fullText);
// sw.Flush();
// sw.Close();
// sw.Dispose();
}
}
catch(Exception ex)
{
}
finally
{
Monitor.Exit(lockObject);
}
}
以下是测试代码:
{
class Program
{
private static object lockObject = new object();
static void Main(string[] args)
{
Thread thread0 = Thread.CurrentThread;
thread0.Name = "thread Current";
Thread newThread = new Thread(Program.StartWork);
newThread.Name = "thread 1";
Thread newThread2 = new Thread(Program.StartWork);
newThread2.Name = "thread 2";
Thread newThread3 = new Thread(Program.StartWork);
newThread3.Name = "thread 3";
Thread newThread4 = new Thread(Program.StartWork);
newThread4.Name = "thread 4";
Thread newThread5 = new Thread(Program.StartWork);
newThread5.Name = "thread 5";
newThread.Start(1000);
newThread2.Start(1000);
newThread3.Start(1000);
newThread4.Start(1000);
newThread5.Start(1000);
StartWork(1000);
Console.WriteLine("Done");
Console.Read();
}
public static void StartWork(object n)
{
for (int i = 0; i < Convert.ToInt32(n); i++)
writeLog(i);
}
public static void writeLog(object i)
{
Monitor.Enter(lockObject);
try
{
string logFile = @"D:\log.txt";
string fullText = Thread.CurrentThread.Name.ToString() + "\t" + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + "\t" + i;
//对于非托管资源,利用Using。Using语句确保调用 Dispose,即使在调用对象上的方法时发生异常也是如此.可使得资源自动释放
using (System.IO.StreamWriter sw = System.IO.File.AppendText(logFile))
{
sw.WriteLine(fullText);
//sw.Flush();
//sw.Dispose();
}
}
catch (Exception ex)
{
Console.WriteLine("Exception:" + ex.Message);
}
finally
{
Monitor.Exit(lockObject);
}
#region 有问题的Code
//Monitor.Enter(lockObject);
//System.IO.StreamWriter sw = null;
//try
//{
// string logFile = @"D:\log.txt";
// string fullText = Thread.CurrentThread.Name.ToString() + "\t" + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + "\t" + i;
// sw = System.IO.File.AppendText(logFile);
// sw.WriteLine(fullText);
// sw.Flush();
//}
//catch (Exception ex)
//{
// Console.WriteLine("Exception:" + ex.Message);
//}
//finally
//{
// Monitor.Exit(lockObject);
// sw.Close();
//}
#endregion
}
}
}