同步线程访问和C#写入

时间:2017-12-27 17:54:16

标签: c# multithreading

我有一个用C#编写的多线程端口扫描程序应用程序,我希望在应用程序运行时将一些内容打印到控制台和日志文件中。出于这个原因,我有以下帮助器类,它可以很好地写入日志文件和控制台。

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);
            }
        }
    }
}

问题是,由于我的应用程序是多线程的,有时我会创建多个日志文件,每个都部分写入,有时我会抛出异常,因为一个线程在另一个线程使用时无法访问相同的位置进行写入。有没有办法让我上面的Output类多线程,以便我避免上述问题?

1 个答案:

答案 0 :(得分:2)

使用单个文件和lock策略应该完成这项工作:

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);
    }
}

此处的问题是共享锁。如果对多个方法使用相同的锁,则锁定一个方法也会锁定其他方法(在您的情况下为WriteWriteLine)。它对我来说看起来完全没问题,因为它们是严格相关的...但如果非常频繁地调用这些方法,这可能会造成瓶颈。另一方面,单独的锁会使方法独立运行,这更糟糕。

尝试将两个方法合并为一个,如下所示,因此您不必关心在不同方法上处理锁定:

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);
        }
    }
}