Как записывать в файл при конкурентном доступе к нему в асинхронном режиме?

Проект asp.net core 5.0. Telegram webhook. Контроллер принимает объект класса Update создает файл на сервере для этого пользователя и пишет в него данные, чтобы сохранять ответы пользователя получаемые во время диалога, после окончания диалога файл должен удалятся:

[HttpPost]
    public async Task<IActionResult> Post([FromBody] Update update)
    {

        if (update == null) return Ok();

        List<string> listlogs = new List<string>();
        listlogs.Add(String.Format("Update: Id {0}; Date: {1}; UserName: {2}; Message {3};", update.Id.ToString(), update.Message.Date, update.Message.From.Username, update.Message.Text));
        ReadWriteFileTxt.WriteFile(listlogs, _currentPath, "logs_TelegramBot_" + DateTime.Now.Year + "_" + DateTime.Now.Month + "_" + DateTime.Now.Day, "txt", newpath: "logs");

        var message = update.Message;

        string responseMessage = "";
        if (message != null)
        {
            responseMessage = "Укажите номер в федеральном формате (+7хххххххххх)";

            if (message.ReplyToMessage != null)
            {
                string messageNumberPhone;
                switch (message.ReplyToMessage.Text)
                {
                    case "Укажите фамилию имя отчество полностью, через пробел":
                        string textMessage = message.Text != null ? message.Text.Trim() : null;
                        if (textMessage != null && textMessage.Length > 4)//
                        {
                            string[] fio = message.Text.Split(' ');

                            if (fio.Length > 1)
                            {
                                List<string> fioList = new List<string>();
                                fioList.Add(fio[0]);
                                fioList.Add(fio[1]);
                                if(fio.Length>2) fioList.Add(fio[2]);
                                else fioList.Add(" ");
                                
                                string nameUser = message.From.Username != null ? message.From.Username : message.From.Id.ToString();
                                                                //Запись в файл
                                string fullPath = ReadWriteFileTxt.WriteFile(fioList, _currentPath, nameUser + "_" + DateTime.Now.Year + "_" + DateTime.Now.Month + "_" + DateTime.Now.Day, "txt", newpath: "RegisterUsers");

                                await _telegramBotClient.SendTextMessageAsync(message.Chat.Id, "Укажите номер телефона в федеральном формате (7хххххххххх) для регистрации", replyMarkup: new ForceReplyMarkup { Selective = true });
                            }
                            else
                            {
                                await _telegramBotClient.SendTextMessageAsync(message.Chat.Id, "Вы не указали или указали в неверном формате ФИО. Укажите ФИО полностью через пробел textMessage", replyMarkup: new ForceReplyMarkup { Selective = true });
                            }
                        }
                        else
                        {
                            await _telegramBotClient.SendTextMessageAsync(message.Chat.Id, "Вы не указали или указали в неверном формате ФИО. Укажите ФИО полностью через пробел textMessage != null && textMessage.Length < 5", replyMarkup: new ForceReplyMarkup { Selective = true });
                        }
                        break;
                    case "Укажите номер телефона в федеральном формате (7хххххххххх) для регистрации":
                        messageNumberPhone = ServicePhoneNumber.LeaveOnlyNumbers(message.Text);

                        if (messageNumberPhone.Length < 11 || messageNumberPhone.Length > 12)
                        {
                            await _telegramBotClient.SendTextMessageAsync(message.From.Id, "Номер телефона указан в неверном формате. Ждем Ваш номер для регистрации в федеральном формате (+7хххххххххх)", ParseMode.Default, replyMarkup: new ForceReplyMarkup { Selective = true });
                        }
                        else
                        {
                            string nameUser = message.From.Username != null ? message.From.Username : message.From.Id.ToString();
                            string fullPath = ReadWriteFileTxt.WriteFile(messageNumberPhone, _currentPath, nameUser + "_" + DateTime.Now.Year + "_" + DateTime.Now.Month + "_" + DateTime.Now.Day, "txt", newpath: "RegisterUsers");
                            //Запись в файл
                            List<string> strRegisterList = ReadWriteFileTxt.ReadFile(fullPath);
                            //Удаление
                            if (fullPath != null)
                            {
                                ReadWriteFileTxt.DeleteFile(fullPath);
                            }

                            if (strRegisterList.Count < 4) return Ok("Были указаны не все данные. Попробуйте пройти регистрацию повторно.");

                            MessageRegisterUser messageRegistrationUser = new MessageRegisterUser
                            {
                                famileName = strRegisterList[0],
                                name = strRegisterList[1],
                                patronimicName = strRegisterList[2],
                                phoneNumber = strRegisterList[3]
                            };

                            // Отправка запроса на API др. сервиса
                            string jsonRequestRegistration = JsonSerializer.Serialize(messageRegistrationUser);
                            string jsonResponseData = await PostRequestHttpAsync(urlRequestRegistration, jsonRequestRegistration);
                            ResponseMessageData responseData = JsonSerializer.Deserialize<ResponseMessageData>(jsonResponseData);

                            await _telegramBotClient.SendTextMessageAsync(message.Chat.Id, messageRegistrationUser.name +","+ responseData.status);

                        }
                        break;

                }                    
            }
            else if (message.Text.Equals(commandGet))
            {
                await _telegramBotClient.SendTextMessageAsync(message.From.Id, responseMessage, ParseMode.Default, replyMarkup: new ForceReplyMarkup { Selective = true });
            }
            else if (message.Text.Equals(commandRegistration))
            {
                await _telegramBotClient.SendTextMessageAsync(message.Chat.Id, "Укажите фамилию имя отчество полностью, через пробел", replyMarkup: new ForceReplyMarkup { Selective = true });
            }
            else
            {
                responseMessage = "Выберите пункт меню:";
                await _telegramBotClient.SendTextMessageAsync(message.From.Id, responseMessage, replyMarkup: GetMenuButtons());
            }
        }

        return Ok();
    }

Класс записи и удаления файла:

public class ReadWriteFileTxt
{
    static char[] charsToTrim = { ' ', '\n', '\r', '\'', '\'' };

    public static List<string> ReadFile(string filePath)
    {
        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

        List<String> fileContentList = new List<string>();

        StreamReader fileStream = new StreamReader(filePath, Encoding.UTF8);//Encoding.GetEncoding("Windows-1251")

        while (!fileStream.EndOfStream)
        {
            string str = fileStream.ReadLine().Trim().Trim(charsToTrim);
            if(str != null && !str.Equals("")) fileContentList.Add(str);
        }
        return fileContentList;
    }

    public static string WriteFile(List<string> listWrite, string path, string nameFile, string typeFile, string newpath)
    {
        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
        DirectoryInfo dirInfo = new DirectoryInfo(path);
        DirectoryInfo newFolder = dirInfo.Parent.CreateSubdirectory(newpath);
        if (!newFolder.Exists)
        {
            newFolder.Create();
        }

        string fullPath = String.Format(@"{0}\{1}.{2}", newFolder.FullName, nameFile, typeFile);
        // This text is added only once to the file.
        try
        {
            if (!File.Exists(fullPath))
            {
                using (StreamWriter writer = new StreamWriter(fullPath, false, new UTF8Encoding(false)))
                {

                    foreach (string str in listWrite)
                    {
                        writer.WriteLine(str);
                    }
                }
            }
            else
            {
                using (StreamWriter writer = new StreamWriter(fullPath, true, new UTF8Encoding(false)))
                {
                    foreach (string str in listWrite)
                    {
                        writer.WriteLine(str);
                    }
                }
            }
            return fullPath;
        }
        catch {
            return fullPath;
        }                
    }

    public static string WriteFile(string str, string path, string nameFile, string typeFile, string newpath)
    {
        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
        DirectoryInfo dirInfo = new DirectoryInfo(path);
        DirectoryInfo newFolder = dirInfo.Parent.CreateSubdirectory(newpath);
        if (!newFolder.Exists)
        {
            newFolder.Create();
        }

        string fullPath = String.Format(@"{0}\{1}.{2}", newFolder.FullName, nameFile, typeFile);
        // This text is added only once to the file.
        try
        {
            if (!File.Exists(fullPath))
            {
                using (StreamWriter writer = new StreamWriter(fullPath, false, new UTF8Encoding(false)))
                {
                    writer.WriteLine(str);
                }
            }
            else
            {
                using (StreamWriter writer = new StreamWriter(fullPath, true, new UTF8Encoding(false)))
                {
                   writer.WriteLine(str);
                }
            }
            return fullPath;
        }
        catch {

            return null;            
        }
    }
    

    public static void DeleteFile(string pathFull)
    {
        try
        {
            File.Delete(pathFull);
        }
        catch(Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

Мне выдаёт исключения о том, что я передаю нулевой путь к файлу:

An unhandled exception has occurred while executing the request. System.ArgumentNullException: Value cannot be null. (Parameter 'path') at System.IO.StreamReader.ValidateArgsAndOpenPath(String path, Encoding encoding, Int32 bufferSize) at TLmessanger.Services.ReadWriteFileTxt.ReadFile(String filePath) in D:\My_PROGRAMS\TLmessanger\Services\ReadWriteFileTxt.cs:line 20 at TLmessanger.Controllers.TelegramListenerController.Post(Update update) in D:\My_PROGRAMS\TLmessanger\Controllers\TelegramListenerController.cs:line 270 at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)

Нулевой путь у меня возвращает метод записи, если ему не удалось произвести запись в файл:

try
        {
            if (!File.Exists(fullPath))
            {
                using (StreamWriter writer = new StreamWriter(fullPath, false, new UTF8Encoding(false)))
                {
                    writer.WriteLine(str);
                }
            }
            else
            {
                using (StreamWriter writer = new StreamWriter(fullPath, true, new UTF8Encoding(false)))
                {
                   writer.WriteLine(str);
                }
            }
            return fullPath;
        }
        catch {

            return null;            
        }

А запись не получается произвести потому что, если я правильно понял, идет конкурентный доступ к файлу - не закончилась запись предыдущего потока. Как можно это исправить?

Вариант после исправлений замечаний:

    public class ReadWriteFileTxt
    {
        static char[] charsToTrim = { ' ', '\n', '\r', '\'', '\'' };
        private static readonly object _locker = new object();
        private static readonly UTF8Encoding encoder = new UTF8Encoding(false);

       public static List<string> ReadFile(string filePath)
        {
            //Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
            List<string> fileContentList = new List<string>();

            StreamReader fileStream = new StreamReader(filePath, encoder);//Encoding.GetEncoding("Windows-1251")

            while (!fileStream.EndOfStream)
            {
                string str = fileStream.ReadLine().Trim().Trim(charsToTrim);
                if(str != null && !str.Equals("")) fileContentList.Add(str);
            }
            return fileContentList;
        }

        public static string WriteFile(List<string> listWrite, string path, string nameFile, string typeFile, string newpath)
        {
            DirectoryInfo dirInfo = new DirectoryInfo(path);
            DirectoryInfo newFolder = dirInfo.Parent.CreateSubdirectory(newpath);
            try
            {
                if (!newFolder.Exists)
                {
                    newFolder.Create();
                }
            }
            catch (Exception ex) { Console.WriteLine(ex.Message); }

            string fullPath = Path.Combine(newFolder.FullName, $"{nameFile}.{typeFile}");
            lock (_locker)
            {
                try
                {
                    File.AppendAllLines(fullPath,listWrite);
                    return fullPath;
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    return null;
                }
            }

        }

        public static string WriteFile(string str, string path, string nameFile, string typeFile, string newpath)
        {
            DirectoryInfo dirInfo = new DirectoryInfo(path);
            DirectoryInfo newFolder = dirInfo.Parent.CreateSubdirectory(newpath);            

            string fullPath = Path.Combine(newFolder.FullName, $"{nameFile}.{typeFile}");
            // This text is added only once to the file.

            try
            {
                if (!newFolder.Exists)
                {
                    newFolder.Create();
                }
            }
            catch (Exception ex) { Console.WriteLine(ex.Message);  Console.WriteLine(ex.Message); }

            lock (_locker)
            {
                try
                {
                    File.AppendAllText(fullPath, str + Environment.NewLine);
                    return fullPath;
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    return null;
                }
            }
        }

        public static List<string[]> SplitString(List<string> stringList, char parserChar)
        {
            List<string[]> splitStringList = new List<string[]>(stringList.Count);

            for (int i = 0; i < stringList.Count; i++)
            {
                string[] valueColumns = stringList[i].Split(parserChar);
                splitStringList.Add(valueColumns);
            }

            return splitStringList;
        }

        public static void DeleteFile(string pathFull)
        {
            try
            {
                File.Delete(pathFull);
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }    
}

Исключение ещё осталось, оно указывает на строку метода чтения - 21:

StreamReader fileStream = new StreamReader(filePath, encoder);

и строку из которой вызывается метод чтения - 270:

string fullPath = ReadWriteFileTxt.WriteFile(messageNumberPhone, _currentPath, nameUser + "_" + $"{DateTime.Now:yyyy_MM_dd}", "txt", newpath: "RegisterUsers");

fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1] An unhandled exception has occurred while executing the request. System.ArgumentNullException: Value cannot be null. (Parameter 'path') at System.IO.StreamReader.ValidateArgsAndOpenPath(String path, Encoding encoding, Int32 bufferSize) at TLmessanger.Services.ReadWriteFileTxt.ReadFile(String filePath) in D:\My_PROGRAMS\TLmessanger\Services\ReadWriteFileTxt.cs:line 21 at TLmessanger.Controllers.TelegramListenerController.Post(Update update) in D:\My_PROGRAMS\TLmessanger\Controllers\TelegramListenerController.cs:line 270


Ответы (0 шт):