Thread Safe Code с System.IO StreamWriter клас

Бих искал да генерирам от 1 до 0,1 милиона числа, за да получа уникален идентификатор за името на моя файл. За да постигна това, създадох едно статично свойство, като използвах заключване, както следва: -

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

Забележка Не искам да генерирам уникален идентификатор за изхвърляне, тъй като може да е дубликат или комбинация от дата и час [Опитах и ​​двете, създаваше се дубликат]. И в моя случай, ако горният метод се провали след 0,1 милиона, това е файл за мен [Тъй като горният код ще използвам за уникално име на файл и файлът се създава в партида от около 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), за 0,1 милиона записа, изглежда, че файлът се дублира, тъй като получавам изключение „файлът се използва от друг процес“ , но ако първият код е безопасен за нишка, тогава той не трябва да създава дублиран идентификатор до 0,1 милиона. Какво не е наред тук, както го наричам? или проблемът с класа 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);
            }
        }

След това също не проработи.

Решение

О, БОЖЕ МОЙ!! Разбрах виновника... проблем със следния ред код

_fileName = _InitfileName + statInt;

Премахнах този ред и директно го предадох на метода.

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

Това беше луда грешка, създадох единичен екземпляр на класа и го предадох в нишката,

Оценявам коментара на Марвин,

"_fileName = _InitfileName + statInt; четенето без заключване, докато се записва по безопасен начин, може да доведе до дубликати."

той го забеляза много скоро, но по-късно и двамата тръгнахме в различна посока.

Благодаря за коментарите на всички тук


person vikas    schedule 30.04.2014    source източник
comment
Да, безопасен е за нишки. Процесът не е безопасен. С други думи, лошите неща се случват, когато стартирате програмата си повече от веднъж. Не трябва да се изпълнява едновременно, за да предизвика това изключение. Използването на класа System.Guid е лесен начин за избягване на сблъсъци на имена на файлове.   -  person Hans Passant    schedule 30.04.2014
comment
@HansPassant Съжалявам, процесът е безопасен, моля, изяснете какво означава това   -  person vikas    schedule 30.04.2014
comment
_fileName = _InitfileName + statInt; четенето без заключване, докато се записва по безопасен начин, може да доведе до дублиране. (т.е. thr1 = counter++; thr2=readcounter(),thr3=readcounter() =› дублирано четене. Ps; вижте Interlocked.Increment(ref x) за безопасно броене на нишки (по-лесно).   -  person Marvin Smit    schedule 30.04.2014
comment
@MarvinSmit мнението ви изглежда валидно за мен, просто се надявам, че ако запазим _filename в ключалка или го запазим като нестатичен, трябва да работи нали?   -  person vikas    schedule 30.04.2014
comment
Имате други проблеми (като използване на неконкурентен речник във вашия многонишков код). Бих потърсил начин да накарам x нишки да изпълняват обработката, получавайки уникално име на файл в началото на тяхното изпълнение. Също така обмислете използването на ThreadPool или Task библиотека за изпълнение на тези задачи вместо класа Thread от ниско ниво. Те дават много опции (като сигнализиране и изчакване), които иначе трябва да внедрите сами (клас ManualResetEvent и други подобни)   -  person Marvin Smit    schedule 30.04.2014
comment
@MarvinSmit Премахнах dictionary (използвах го за отстраняване на грешки), а също така направих _fileName и _InitfileName като нестатични, не работи, така че бях написал също ThreadPool клас и го бях проверил преди това, не работеше, но тъй като току-що сменихме тези променливи като нестатични, така че може да работи сега, позволете ми да опитам веднъж с помощта на ThreadPool   -  person vikas    schedule 30.04.2014
comment
@MarvinSmit Бях използвал и ThreadPool, но това няма да помогне и дори както е посочено от Gusdor, бях компромисирал с 65 000, а не с 100 000 сега   -  person vikas    schedule 30.04.2014
comment
Трябва да направите _fileName локален за метода, а не член на класа. Така ще имате: _fileName = _InitfileName + statInt;   -  person Jim Mischel    schedule 30.04.2014
comment
@JimMischel по този начин можем да постигнем, мисля, че казвате, че можем да предадем стойността на името на файла в метода от извикващ код, нали?   -  person vikas    schedule 30.04.2014
comment
след толкова много коментари все още се съмнявам в първия си въпрос Безопасна ли е кодовата нишка?\   -  person vikas    schedule 30.04.2014
comment
Вашето свойство statInt е безопасно за нишки. Останалата част от вашия код страда от неразбиране на разликите между статичните полета, полетата на екземплярите и локалните променливи и следователно не е плашещо безопасен за нишки.   -  person Jim Mischel    schedule 30.04.2014
comment
@JimMischel _fileName и _InitfileName сега не са статични и дори аз също не използвам статичен екземпляр на Dictionary   -  person vikas    schedule 30.04.2014
comment
@JimMischel също не използва stateDate, което също взех статичен, а също така има само две статични променливи сега objLock и _statInt   -  person vikas    schedule 30.04.2014


Отговори (3)


Няма значение колко ключалки сте поставили около вашия FileStream файлът не може да бъде отворен повече от веднъж едновременно. Това включва други приложения, които могат да имат отворен файл. Изключението, което получавате, ви казва, че се опитвате да отворите един и същ файл повече от веднъж - вашият код за рандомизиране на пътя е повреден.

Има изключения - файлове, които се отварят с изричен ниво на споделяне на файлове.

Всяко ваше обаждане до 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

person Gusdor    schedule 30.04.2014
comment
Не заключих FileStream, основната грижа тук е, че предавам уникално име на файл и защо FileStream хвърля тази грешка - person vikas; 30.04.2014
comment
да, това е в друга нишка и след това се обаждам на statint, за да получа правилно уникалното име на файла, така че какъв е проблемът? - person vikas; 30.04.2014
comment
@vikas проблемът е, че не генерирате уникално име на файл. виж моите актуализации - person Gusdor; 30.04.2014
comment
Мога да използвам System.IO.Path.GetTempFileName, но погледнете по-късно, ако не е постижимо по моята собствена логика :) - person vikas; 30.04.2014
comment
само като чета статията, която споделихте, изглежда, че е валидна само за 65535? ›› Методът GetTempFileName ще предизвика IOException, ако се използва за създаване на повече от 65535 файла без изтриване на предишни временни файлове. - person vikas; 30.04.2014
comment
@vikas изглежда разумно - 100 000 едновременни нишки няма да постигнат нищо за вас. Имате много по-малко ядра във вашия процесор и твърдият ви диск вероятно не може да направи толкова много едновременни записи. System.Threading.Parallel.For ще планира работата и ще създаде нишките вместо вас. Може да бъде ограничено до максимум 65535 едновременни операции с това решение -› stackoverflow.com/a/15931412/286976 - person Gusdor; 30.04.2014

Мисля, че проблемът ви е тук:

Декалирате _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 и ще тръгнете в правилната посока.

Въпреки това съм съгласен с Gusdor, че трябва да разгледате Parallel.For() и Path.GetTempFileName().

person Jon B    schedule 30.04.2014
comment
Направих промени в _fileName, направих го статичен, но не помогна, а също така не искам да преминавам към решение 4.0+, тъй като го използвахме и за предишен рамков код, но сега разбрах виновника, проблем с начина, по който го присвоявах, така че направих тези промени и сега не само за 65 000 или нещо подобно, работи и за 100 000 :) - person vikas; 02.05.2014

Мисля, че можете да постигнете това, което искате, като използвате следния код (.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
            }
        );
    }

Надявам се това да помогне.

person Marvin Smit    schedule 30.04.2014
comment
Не искам да преминавам към решение 4.0+, тъй като го използвахме и за предишен рамков код, но получих решението, правех луда грешка :). Но най-накрая разбрах виновника, благодаря за помощта - person vikas; 02.05.2014
comment
бихте ли споменали коментара си във вашия отговор (за да маркирате тази публикация като отговор)- _fileName = _InitfileName + statInt; четенето без заключване, докато се записва по безопасен начин, може да доведе до дублиране. (т.е. thr1 = counter++; thr2=readcounter(),thr3=readcounter() =› дублирано четене. Ps; вижте Interlocked.Increment(ref x) за нишково безопасно броене (по-лесно). , беше много полезно да се намери решението. - person vikas; 25.06.2014