better way of invoking thread and logging to text file in C# - c#

I have a program which write logging as Text File.
namespace logging
{
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
public class log
{
private static string lpath;
[DllImport("kernel32")]
private static extern int GetPrivateProfileString(string section, string key, string def, StringBuilder retVal, int size, string filePath);
public static void Info(string user, string info, string txt)
{
StreamWriter writer;
string str = Assembly.GetExecutingAssembly().GetName().CodeBase.ToString();
.....
if (!File.Exists(path))
{
writer = new StreamWriter(path, true);
writer.WriteLine(str3 + " " + info + " => " + txt);
writer.Flush();
writer.Close();
}
else
{
writer = File.AppendText(path);
writer.Write(str3 + " " + info + " => " + txt + "\n");
writer.Flush();
writer.Close();
}
}
}
}
Then I have a function.
private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Thread thread_BP_Print = new Thread(BoardingPass_Print);
thread_BP_Print.Start();
// Simultaneously, do something on the main thread.
BaggageTag_Print();
bgw.RunWorkerAsync();
}
Two of the inner functions (BoardingPass_Print() and BaggageTag_Print()) call the same logging function.
logging.log.Info(username, "Query Trace > ", sql);
Because of my invoking thread methods, I think I face the error:
The process cannot access the file 'C:\Folder\2012-01-17-Name.txt' because it is being used by another process.
Could anyone give me better solution to interact with thread and logging to text file without facing error message like this?
Every suggestion will be really appreciated.

You would have to make the writing thread-safe (with lock). Your current code also is not exception-safe.
You can solve both issues by using the right library method.
You cannot (easily) assure safe access to a file from multiple processes, but as long as its only written by 1 instance of the application you can sync the threads.
private static object locker = new object();
public static void Info(string user, string info, string txt)
{
string str = Assembly.GetExecutingAssembly().GetName().CodeBase.ToString();
string str3 = ...
lock (locker)
{
File.AppendAllText(path, str3 + " " + info + " => " + txt + "\n");
}
}
AppendAllText is a static method, it will handle the new/existing file issue and the resource management correctly.

You could use log4net, which is thread safe, instead of writing your own log function.

The classic version would be:
Have a dedicated logger thread, most of the time waiting on an
AutoResetEvent
Have a (threadsafe) Queue
Logging is initiated by writing to that Queue and setting the
AutoResetEvent
The logger thread wakes up, empties the queue into the file, then
sleeps again
You can chose from a choice of predeveloped frameworks for that or roll your own in a few lines, if you want to avoid the dependency.

Like others have said use NLog, log4Net, Enterprise Library, etc. as rolling your own logging logic will not be a comprehensive as the offerings already available.
However if you want to keep things as they are... one suggestion might be to send the log information to a queue (say MSMQ) and have another separate process poll the queue and save the information to your text files.

If you don't want to use any logging framework, you must at least use lock statement to be thread-safe.
You can also use Singleton instead of static method and keep your log file open until appliaction ends.

Related

System.IO.IOException: The process cannot access the file '.txt' because it is being used by another process

I am using the next code to log errors of an web application.
using (StreamWriter myStream = new StreamWriter(sLogFilePath, true))
{
myStream.WriteLine(string.Format("{0, -45}{1, -25}{2, -10 {3}", guid, DateTime.Now, StringEnum.GetStringValue(enumMsg), sText));
}
Sometimes, the following exception 'System.IO.IOException: The process cannot access the file '.txt' because it is being used by another process.' is thrown.
I think this is caused by multiple instances of the web app at the same time. Can you help me fix this problem, please ?
EDIT: I have to add that for every method I log like this:
Date - Method X started.
Date - Exception.Message (table not found or other errors)
Date - Method X stopped.
and when this Error appears, it's logged only this:
Date - System.IO.IOException: The process cannot access the file '.txt' because it is being used by another process.
Sadly Windows does not allow waiting on a file lock. In order to get around this all your applications will have to create a lock that all the processes involved can check.
The use of this code will only prevent threads within a single process from accessing the file:
/* Suitable for a single process but fails with multiple processes */
private static object lockObj = new Object();
lock (lockObj)
{
using (StreamWriter myStream = new StreamWriter(sLogFilePath, true))
{
myStream.WriteLine(string.Format("{0, -45}{1, -25}{2, -10 {3}", guid, DateTime.Now, StringEnum.GetStringValue(enumMsg), sText));
}
}
In order to lock across multiple processes a Mutex lock is required. This gives a name to the lock that other processes can check for. It works like this:
/* Suitable for multiple processes on the same machine but fails for
multiple processes on multiple machines */
using (Mutex myMutex = new Mutex(true, "Some name that is unlikly to clash with other mutextes", bool))
{
myMutex.WaitOne();
try
{
using (StreamWriter myStream = new StreamWriter(sLogFilePath, true))
{
myStream.WriteLine(string.Format("{0, -45}{1, -25}{2, -10 {3}", guid, DateTime.Now, StringEnum.GetStringValue(enumMsg), sText));
}
}
finally
{
myMutex.ReleaseMutex();
}
}
I don't think Mutexes can be access from remote machines so if you have a file on a file share and you are trying to write to it from processes on multiple machines then you are probably better off writing a server component on the machine that hosts the file to mediate between the processes.
Your web server will run requests in multiple threads. If two or more requests have to log exceptions at the same time, this will lead to the exception you see.
You could either lock the section as James proposed, or you could use a logging framework that will handle multithreading issues for you, for example Lgo4net or NLog.
Assuming you need each thread to eventually write to the log, you could lock the critical section
private static object fileLock = new Object();
...
lock (fileLock)
{
using (StreamWriter myStream = new StreamWriter(sLogFilePath, true))
{
myStream.WriteLine(string.Format("{0, -45}{1, -25}{2, -10 {3}", guid, DateTime.Now, StringEnum.GetStringValue(enumMsg), sText));
}
}
This means only 1 thread at any given time can be writing to the file, other threads are blocked until the current thread has exited the critical section (at which point the file lock will have been removed).
One thing to note here is that lock works per process, therefore if your site is running the context of a web farm/garden then you would need to look at a system-wide locking mechanism i.e. Mutexes.
I've added this code to my class:
public static bool IsFileLocked(FileInfo file)
{
FileStream stream = null;
try
{
stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}
catch
{
return true;
}
finally
{
if (stream != null)
{
stream.Close();
}
}
return false;
}
and now my LogToFile method is like this:
while (IsFileLocked(fi))
{
}
using (StreamWriter myStream = new StreamWriter(sLogFilePath, true))
{
if (displayTime == true)
myStream.WriteLine(string.Format("{0, -45}{1, -25}{2, -10}{3}", guid, DateTime.Now, StringEnum.GetStringValue(enumMsg), sText));
else
myStream.WriteLine(string.Format("{0, -70}{1, -10}{2} ", guid, StringEnum.GetStringValue(enumMsg), sText));
}
I hope this will work.

Performance issues using task factory in C#

I am working on a logging system for a web application which logs a sequence of events in a dictionary object before sending it to my logging object using Task.Factory.StartNew(() => iLogEventSave()). The logger seemed to work fine, but in some instances some events were not being saved properly so I used the lock() statement to correct the issue. This seemed to do the trick, but the application's performance has dramatically decreased by doing this. How can I have the UI/Page render without having to wait for the Tasks to finish their job?
Below is the code
private static readonly object Locker = new object();
public void iLogEventSave(object state)
{
XmlDocument doc = new XmlDocument();
IDictionary<string, string> EventDetails = (IDictionary<string, string>)state;
string logFile = "";
if(ConfigurationManager.AppSettings["Log_File_Path"].ToString() =="")
{
logFile = HttpRuntime.AppDomainAppPath + "Logs\\" + DateTime.Now.ToString("yyyy_MM_dd") + ".txt";
}
else
{
logFile = ConfigurationManager.AppSettings["Log_File_Path"].ToString() + DateTime.Now.ToString("yyyy_MM_dd") + ".txt";
}
lock (Locker)
{
if (File.Exists(logFile))
{
doc.Load(logFile);
}
else
{
var root = doc.CreateElement("Log");
doc.AppendChild(root);
}
var el = (XmlElement)doc.DocumentElement.AppendChild(doc.CreateElement("Event"));
foreach (KeyValuePair<string, string> item in EventDetails)
{
XmlElement Desc = doc.CreateElement("Details");
Desc.SetAttribute(item.Key.ToString(), item.Value);
el.AppendChild(Desc);
}
doc.Save(logFile);
}
}
If your log did not save several events while being executed asynchronously, you have an unhandled error that you did not address. Considering that you're using a file, I'm going to go out on a limb and say that it failed because two threads were competing for access to the same log file and the first thread to grab it locked the other one out. This is why your lock would now work, it prevents other threads from trying to grab the file.
But logging to a file means that you've effectively restricted yourself to one thread at a time and dealing with the entire file as it grows. You have to load more and more, append more and more, and locking the thread means that the more threads are waiting to log the events, the higher your overhead. All this could certainly add up to a decrease in performance.
May I recommend using a database table to log events? File I/O is very expensive, resource and time-wise. Databases have less overhead and far better throughput by comparison in these very scenarios.

Error Logging in C# Website

I am planning to implement error logging writing something similar to this:
public static void WriteError(string errorMessage)
{
try
{
string path = "~/Error/" + DateTime.Today.ToString("dd-mm-yy") + ".txt";
if (!File.Exists(System.Web.HttpContext.Current.Server.MapPath(path)))
{
File.Create(System.Web.HttpContext.Current.Server.MapPath(path)).Close();
}
using (StreamWriter w = File.AppendText(System.Web.HttpContext.Current.Server.MapPath(path)))
{
w.WriteLine("\r\nLog Entry : ");
w.WriteLine("{0}", DateTime.Now.ToString(CultureInfo.InvariantCulture));
string err = "Error in: " + System.Web.HttpContext.Current.Request.Url.ToString() +
". Error Message:" + errorMessage;
w.WriteLine(err);
w.WriteLine("__________________________");
w.Flush();
w.Close();
}
}
catch (Exception ex)
{
WriteError(ex.Message);
}
}
My question is this: Since it is a website, I will have multiple users simultaneously. In that case, if multiple users encounter exceptions at the same time and try to write to the same file, it will again give me an exception right? In that case, how do I implement error logging correctly for simultaneous users? Or will this work?
Thanks in advance.
I'd recommend you use an established logging framework such as NLog or log4net. I have used log4net on many projects in the past and it handles this scenario perfectly.
EDIT:
Just as a further comment, as well as handling log messages from multiple threads log4net also allows you to manage how large your log files grow and provide a built in rolling log mechanism via the RollingFileAppender.
Beside using NLog or other log library, the way you lock this cases is with mutex. I suggest mutex, and not lock() because can catch all pools/threads that may throw an error.
On MSDN there are the details about mutex and examples:
http://msdn.microsoft.com/en-us/library/system.threading.mutex.aspx
a simple example
public static void WriteError(string errorMessage)
{
var mut = new Mutex(true, "LogMutexName");
try
{
// Wait until it is safe to enter.
mut.WaitOne();
// here you open write close your file
}
finally
{
// Release the Mutex.
mut.ReleaseMutex();
}
}

Why thread is stopped when start new process, while application (c#) is still running?

Is there any problem which i have to do carefully when starting new process in multiple thread application?
I tried this in a simple project:
static void Main(string[] args)
{
Process.Start(#"D:\System\Desktop\a.txt");
MessageBox.Show("Success");
}
And it runs perfectly. But when i do it in my big project which use multiple thread, it's thread is stopped working ("a.txt" is opened but "Success" is not shown) while my application (other thread) do well.
What is the problem in this situation?
If you have a Windows.Forms application and you try to show a message-box from a thread that is not the main user-interface thread, the behavior of the message-box is undefined. Meaning, it may or may not show, be inconsistent, or some other problem.
For instance, displaying a message-box from the BackgroundWorker's DoWork event may or may not work. In one case, the message-box-result was always cancel regardless of what button was clicked.
Therefore, if you are using a message-box just for debugging purposes, use a different technique. If you have to show a message-box, call it from the main user-interface thread.
A console-application should normally not have problems displaying message-boxes. Yet, I have had cases where I would have to sleep the thread for 100ms before the message-box call.
Note, as TomTom pointed out, the main user-interface thread is the application's Windows message loop. Which reminds me, I once had to create a Form in a Console application in order to create a Windows message loop, so my application could respond to Windows messages.
This isn't the answer - I can't put all this code in a comment...
This works for me. Tell me how your code differs from this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading;
using System.IO;
namespace Test
{
class Program
{
const string OutputFile = #"E:\Output.txt";
object _lock = new object();
static void Main(string[] args)
{
Program program = new Program();
Thread thread = new Thread(program.ThreadMethod);
thread.Start(#"E:\Test.txt");
thread = new Thread(program.ThreadMethod);
thread.Start(#"E:\DoesntExist.txt");
Console.ReadKey();
}
void ThreadMethod(object filename)
{
String result = RunNormal(filename as string);
lock (_lock)
{
FileInfo fi = new FileInfo(OutputFile);
if (!fi.Exists)
{
try
{
fi.Create().Close();
}
catch (System.Security.SecurityException secEx)
{
Console.WriteLine("An exception has occured: {0}", secEx.Message);
return;
}
}
StreamWriter sw = fi.AppendText();
sw.WriteLine(result);
sw.Close();
}
}
string RunNormal(string fullfilename)
{
try
{
Process.Start(fullfilename);
return fullfilename + "|Success";
}
catch (Exception e)
{
return fullfilename + "|" + e.ToString();
}
}
}
}
The output in Output.txt is:
E:\DoesntExist.txt|System.ComponentModel.Win32Exception (0x80004005): The system cannot find the file specified
at System.Diagnostics.Process.StartWithShellExecuteEx(ProcessStartInfo startInfo)
at System.Diagnostics.Process.Start()
at System.Diagnostics.Process.Start(ProcessStartInfo startInfo)
at System.Diagnostics.Process.Start(String fileName)
at Test.Program.RunNormal(String fullfilename) in E:\Projekti\VS2010\Test\Test\Program.cs:line 59
E:\Test.txt|Success
How much different is your code? Do you call some other methods? How do you process the results?
Make sure Process.Start works. Passing a filename is not good enough in some cases. In you sample code, you would have to set the use-shell property; otherwise, you would have to use cmd start <filename> or equivalent.
Therefore, just start NotePad.exe to make sure Process.Start works. If it does then your problem is the process command and command line.

The process cannot access the file because it is being used by another process. (threads)

I write my debug information to a file using a separate thread. During startup, I like to backup any previous file. Unfortunately, it seems the OS hangs onto the file handle for an indeterminate amount of time so when I try to write to the file, it fails.
I am using C#, .Net framework 3.5 on Windows XP. (Vista and Win7 have the same problem).
Here is the code that distills the problem, where t will throw a System.IO.IOException: "The process cannot access the file 'C:\deleteMe.txt' because it is being used by another process."
public class WriteToFile {
static void Main(){
String filename=#"C:\deleteMe.txt";
String filenameBackup = #"C:\deleteMe (backup).txt";
String value = "this is a test value";
//MAKE FILE
fillFile(filename, value);
//MAKE A THREAD TO WRITE TO FILE, WHEN READY
Semaphore readyToWrite=new Semaphore(1, 1);
var t=new Thread(
new ThreadStart(delegate(){
readyToWrite.WaitOne();
WriteToFile.fillFile(filename, value);
})
);
t.Priority=ThreadPriority.Highest;
t.Start();
//BACKUP FILE
if (File.Exists(filename)) {
File.Delete(filenameBackup);
File.Copy(filename, filenameBackup);
File.Delete(filename);
}//endif
//SIGNAL THREAD TO WRITE TO FILE
readyToWrite.Release();
}//method
public static void fillFile(String filename, String value) {
try {
StreamWriter w = File.AppendText(filename);
using (w) {
w.Write(value);
w.Flush();
}//using
} catch (Exception e) {
throw new Exception("Can not write to file", e);
}//try
}//method
}//class
Thanks!
You are initializing the Semaphore wrong. Try : new Semaphore(0, 1);
See this MSDN page.
And a WaitEvent is probably easier and more appropriate for this task.
The 2 code changes would be:
//Semaphore readyToWrite=new Semaphore(1, 1);
var readyToWrite = new ManualResetEvent(false);
//readyToWrite.Release();
readyToWrite.Set();
Also, setting the Priority is usually a bad idea. You're not going to gain anything here (it's an I/O thread), so best leave it.

Categories