Reputation: 3424
I have a multithreaded port scanner app written in C#, and I want to print some stuff both to the console and a log file as the app runs. For this reason, I have the following helper class, which works fine in writing both to a log file and the console.
public class Output
{
private const string LOG_DIRECTORY = "Logs";
private readonly string LogDirPath = Path.Combine(Directory.GetCurrentDirectory(), LOG_DIRECTORY);
private static Output _outputSingleton;
private static Output OutputSingleton {
get {
if (_outputSingleton == null)
{
_outputSingleton = new Output();
}
return _outputSingleton;
}
}
public StreamWriter SW { get; set; }
public Output()
{
EnsureLogDirectoryExists();
InstantiateStreamWriter();
}
~Output()
{
if (SW != null)
{
try
{
SW.Dispose();
}
catch (ObjectDisposedException) { } // object already disposed - ignore exception
}
}
public static void WriteLine(string str)
{
Console.WriteLine(str);
OutputSingleton.SW.WriteLine(str);
}
public static void Write(string str)
{
Console.Write(str);
OutputSingleton.SW.Write(str);
}
private void InstantiateStreamWriter()
{
long ticks = DateTime.Now.Ticks;
string logFilename = "scan_" + ticks.ToString() + ".txt";
string filePath = Path.Combine(LogDirPath, logFilename);
try
{
SW = new StreamWriter(filePath);
SW.AutoFlush = true;
}
catch (UnauthorizedAccessException ex)
{
throw new ApplicationException(string.Format("Access denied. Could not instantiate StreamWriter using path: {0}.", filePath), ex);
}
}
private void EnsureLogDirectoryExists()
{
if (!Directory.Exists(LogDirPath))
{
try
{
Directory.CreateDirectory(LogDirPath);
}
catch (UnauthorizedAccessException ex)
{
throw new ApplicationException(string.Format("Access denied. Could not create log directory using path: {0}.", LogDirPath), ex);
}
}
}
}
The problem is that since my app is multithreading sometimes I get multiple log files created, each of them partially written, and sometimes I get exception thrown since one thread cannot access the same place to write while it is being used by another thread. Is there any way to make my above Output
class multithreaded too, so that I avoid the above mentioned problems?
Upvotes: 1
Views: 662
Reputation: 23675
Using a single file and a lock
strategy should do the job:
private Object m_Lock = new Object();
public static void WriteLine(string str)
{
lock (m_Lock)
{
Console.WriteLine(str);
OutputSingleton.SW.WriteLine(str);
}
}
public static void Write(string str)
{
lock (m_Lock)
{
Console.Write(str);
OutputSingleton.SW.Write(str);
}
}
private void InstantiateStreamWriter()
{
string logFilename = "Log.txt";
string filePath = Path.Combine(LogDirPath, logFilename);
try
{
SW = new StreamWriter(filePath);
SW.AutoFlush = true;
}
catch (UnauthorizedAccessException ex)
{
throw new ApplicationException(string.Format("Access denied. Could not instantiate StreamWriter using path: {0}.", filePath), ex);
}
}
The problem here comes with the shared lock. If you use the same lock on multiple methods, then locking one method locks other methods as well (in your case Write
and WriteLine
). It looks totally fine to me since they are strictly related... but this may create a bottleneck if the methods are called very frequently. On the other side, separate locks would make the methods run independently and this is even worse.
Try to merge the two methods into one as follows, so you don't have to care about handling locks on separate methods:
public static void WriteLine(String str, Boolean line = true)
{
lock (m_Lock)
{
if (line)
{
Console.WriteLine(str);
OutputSingleton.SW.WriteLine(str);
}
else
{
Console.Write(str);
OutputSingleton.SW.Write(str);
}
}
}
Upvotes: 2