Потокобезопасный код с классом 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, но это не поможет, и даже, как указал Гусдор, я скомпрометировал 65000, а не 100000 сейчас   -  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? Он разработан именно для вашей ситуации.

Создает на диске временный файл с уникальным именем и размером 0 байт и возвращает полный путь к этому файлу.

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, если он используется для создания более 65 535 файлов без удаления предыдущих временных файлов. - 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, и вы пойдете в правильном направлении.

Однако я согласен с Гусдором в том, что вам следует изучить Parallel.For() и Path.GetTempFileName().

person Jon B    schedule 30.04.2014
comment
Я внес изменения в _fileName, сделал его статическим, но это не помогло, а также я не хочу переходить на решение 4.0+, так как мы использовали его и для предыдущего кода фреймворка, но теперь я получил виновника, проблема с тем, как я его назначал, поэтому я внес эти изменения, и теперь не только для 65000 или около того, он работает и для 100000 :) - 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