使用System.IO StreamWriter类的线程安全代码

时间:2014-04-30 12:23:59

标签: c# multithreading streamwriter .net

我想生成1到10万个数字,以获取我的文件名的唯一ID。为了实现这一点,我使用lock创建了一个静态属性,如下所示: -

 private static readonly object objLock = new object();
        private static int _statInt = 1;

        private static string statInt
        {
            get
            {
                lock (objLock)
                {
                    if (_statInt >= 100000)
                    {
                        _statInt = 1;
                    }
                    else
                    {
                        _statInt = _statInt + 1;
                    }
                    return "_" + _statInt.ToString();
                }
            }
        }

注意我不想生成唯一的id throw guid,因为它可能是重复的或日期时间的组合[我试过它都创建了重复]。在我的情况下,如果上面的方法在10万之后失败,它就是我的文件。[如上面的代码我将用于唯一的文件名,文件创建一个大约5000,然后它被删除]

第一个问题上述代码线程是否安全?

如果是,那么我的第二个原始问题从这里开始: -

我只是在这里保留完整的代码以更好地理解这个问题: -

class Program
    {
        static void Main(string[] args)
        {
            _Interfaceupload obj = new _Interfaceupload();

            for (int i = 0; i < 100000; i++)
            {
                Thread thrd = new Thread(obj.getFileNoDuplicate); 
                thrd.Start();

            }

            Console.ReadLine();
            Dictionary<string, string> obj0 = _Interfaceupload.objDic;
            Console.ReadLine();
        }
    }

    class _Interfaceupload
    {

        private static readonly object objLock = new object();
        private static int _statInt = 0;
        private static string _fileName = "C:/TEST/vikas";
        private static string _InitfileName = "C:/TEST/vikas";
        private static string statInt
        {
            get
            {
                lock (objLock)
                {
                    if (_statInt > 100000)
                    {
                        _statInt = 0;
                    }
                    else
                    {
                        _statInt = _statInt + 1;
                    }
                    return "_" + _statInt.ToString();
                }
            }
        }

        public static string stateDate
        {
            get
            {
                return "_" + DateTime.Now.Ticks.ToString() + "_" + System.Guid.NewGuid();
            }
        }

        public static Dictionary<string, string> objDic = new Dictionary<string, string>();

        public void getFileNoDuplicate()
        {
            try
            {
                //objDic.Add(_InitfileName + statInt, string.Empty);
                // _fileName = _InitfileName + stateDate;
                _fileName = _InitfileName + statInt;
                objDic.Add(FileManager2.Write(_fileName, "txt", "hello", false), string.Empty);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }

class FileManager2
    {
        public static string Write(string file, string ext, string data, bool overwrite)
        {
            return (string)OperateOnFile(file, ext, data, overwrite);
        }

        private static object OperateOnFile(string file, string ext,
           string data, bool overWrite)
        {
            StreamReader sr = null;
            StreamWriter sw = null;
            string workingFile = null;
            string dir = null;

            try
            {
                workingFile = file + "." + ext;
                if (overWrite == false && File.Exists(workingFile))
                {
                    workingFile = (string)OperateOnFile(workingFile + System.Guid.NewGuid().ToString(), ext, data, overWrite);
                }
                else
                {
                    dir = "C:/TEST/";
                    if (!Directory.Exists(dir))
                        Directory.CreateDirectory(dir);

                    sw = new StreamWriter(File.Open(workingFile, FileMode.Create, FileAccess.Write, FileShare.None), Encoding.UTF8);
                    sw.Write(data);
                }
                return workingFile;
            }
            finally
            {
                if (sr != null)
                    sr.Close();

                if (sw != null)
                {
                    sw.Flush();
                    sw.Close();
                }

            }
        }
    }

(可能需要跟随namespaces包含)

using System.Threading;
using System.IO;

第二个问题:当我在控制台应用程序(.NET framework 4.5)中运行它时,对于10万条记录,它看起来该文件正在重复,因为我得到异常&#34;文件正在使用另一个进程&#34;但是如果第一个代码是线程安全的,那么它不应该创建重复的id直到10万。这里有什么问题,我称呼它的方式?或者StreamWriter类或代码问题绕过线程?不确定请告知。

注意我无法锁定File.Exists方法。

被修改 在MarvinSmit的评论使方法成为非静态

之后
  private string _fileName = "C:/TEST/vikas";
            private string _InitfileName = "C:/TEST/vikas";

还删除了词典并将其修改如下: -

public void getFileNoDuplicate()
        {
            try
            {
                //objDic.Add(_InitfileName + statInt, string.Empty);
                // _fileName = _InitfileName + stateDate;
                _fileName = _InitfileName + statInt;
                FileManager2.Write(_fileName, "txt", "hello", false);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

此后也没有工作。

解决方案

OMG !!我得到了罪魁祸首...以下代码行的问题

_fileName = _InitfileName + statInt;

我已删除此行,并直接将其传递给方法。

public void getFileNoDuplicate()
        {
            try
            {
                FileManager2.Write(_fileName + statInt, "txt", "hello", false);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

这是一个疯狂的错误,我创建了一个类的单个实例并将其传递给整个线程,

我很欣赏马文的评论,

&#34; _fileName = _InitfileName + statInt;在以线程安全的方式写入时不被锁定被读取可能导致重复。&#34;

他很快就注意到了,但我们后来都走向了不同的方向。

感谢大家的评论

3 个答案:

答案 0 :(得分:0)

无论您在FileStream文件周围放置多少锁,都无法同时打开多次。这包括可能打开文件的其他应用程序。您收到的异常告诉您,您尝试多次打开同一个文件 - 您的路径随机化代码已损坏。

有一些例外 - 使用明确file share level打开的文件。

您对FileManager2.OperateOnFile的每次通话都会尝试打开该文件。每个电话都是一个不同的线程。

sw = new StreamWriter(File.Open(workingFile, FileMode.Create, FileAccess.Write, FileShare.None), Encoding.UTF8);

你有什么理由不能使用System.IO.Path.GetTempFileName吗?它专为您的情况而设计。

  

在磁盘上创建一个唯一命名的零字节临时文件并返回   该文件的完整路径。

     

http://msdn.microsoft.com/en-us/library/system.io.path.gettempfilename%28v=vs.110%29.aspx

答案 1 :(得分:0)

我认为你的问题在这里:

您将_fileName标记为static

private static string _fileName = "C:/TEST/vikas";

然后你有多个线程在这里为它分配一个值:

public void getFileNoDuplicate()
{
    try
    {
        //objDic.Add(_InitfileName + statInt, string.Empty);
        // _fileName = _InitfileName + stateDate;
        _fileName = _InitfileName + statInt;
        objDic.Add(FileManager2.Write(_fileName, "txt", "hello", false), string.Empty);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

一个线程可能会设置它,然后另一个线程将再次设置它,然后两个线程将尝试写入同一个文件。放下static,你就会朝正确的方向走。

但是,我同意古斯多的观点,你应该调查Parallel.For()Path.GetTempFileName()

答案 2 :(得分:0)

我认为您可以使用以下代码(.Net 4 +)

来实现您的目标
    private static string _basePath = "C:/TEST/vikas/";

    static void Main(string[] args)
    {
        Parallel.For(0, 10000, (index) =>
            {
                string filename = Path.Combine(_basePath, "_" + index.ToString());
                // filename is unique
            }
        );
    }

希望这有帮助。