代码之家  ›  专栏  ›  技术社区  ›  Joan Venge

如何在不锁定磁盘上的文件的情况下读取文本文件?

  •  1
  • Joan Venge  · 技术社区  · 3 年前

    基本上,我有一些这样的代码,可以将ASCII文本文件的内容读取到列表中:

        List<string> lines = new List<string> ( );
        using ( StreamReader sr = File.OpenText ( textfile ) )
        {
            string s = String.Empty;
            while ( ( s = sr.ReadLine ( ) ) != null )
                lines.Add ( s );
        }
    

    但问题是,当文件由另一个线程写入时,它会抛出异常:

    进程无法访问文件“myfile.txt”,因为它正被另一个进程使用。

    同样的事情也发生在File上。读取所有行。为什么这些函数会锁定磁盘上的文件,或者关心文件是否正被另一个进程使用?

    我只是想定期阅读内容,所以如果这次使用它,那么下次它就会阅读更新的内容。我这样做是为了检查是否添加了新条目,因为用户也可以手动添加它们。

    我可以使用哪些函数将此文件读入内存而不会抛出异常,或者我应该在try/catch中运行此代码。

    这是最新的代码:

            var fs = new FileStream ( filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite );
            using ( StreamReader sr = new StreamReader ( fs ) )
            {
                string s = String.Empty;
                while ( ( s = sr.ReadLine ( ) ) != null )
                    lines.Add ( s );
            }
    

    修改文件的代码:

    public static void RemoveCoinFromBuyOrderLogs ( string symbol )
    {
        if ( !walletLocked )
        {
            walletLocked = true;
    
            string [ ] lines = File.ReadAllLines ( walletFilename );
    
            var newlines = lines.Where ( c => !c.StartsWith ( symbol + "USDT" ) && !c.StartsWith ( symbol + "BUSD" ) && !c.StartsWith ( symbol + "USDC" ) && !c.StartsWith ( symbol + "TUSD" ) ).Select ( c => c ).ToList ( );
            File.WriteAllLines ( walletFilename, newlines );
    
            using ( FileStream fs = File.Open ( walletFilename, FileMode.OpenOrCreate ) )
            {
                StreamWriter sw = new StreamWriter ( fs );
                sw.AutoFlush = true;
                newlines.ForEach ( r => sw.WriteLine ( r ) );
            }
    
            walletLocked = false;
        }
    }
    
    public static void AddCoinToOrderLogs ( string newOrder, long orderId )
    {
        if ( !walletLocked )
        {
            var lines = Utility.ReadAllLines ( walletFilename );
            lines = lines.Select ( line => line.Replace ( "\r", "" ) ).ToList ( );
            lines = lines.Where ( line => line != "" ).Select ( line => line ).ToList ( );
    
            var fields = lines.Select ( line => line.Split ( '\t' ) ).ToList ( );
    
            bool duplicate = false;
            foreach ( var field in fields )
            {
                if ( field.Length >= 5 )
                {
                    long id = Convert.ToInt64 ( field [ 4 ] );
                    if ( id == orderId )
                        duplicate = true;
                }
            }
    
            if ( !duplicate )
            {
                lines.Add ( newOrder );
                lines.Sort ( );
    
                walletLocked = true;
                File.WriteAllLines ( walletFilename, lines );
                walletLocked = false;
            }
        }
    }
    
    1 回复  |  直到 3 年前
        1
  •  6
  •   mu88    3 年前

    看看 this overload 属于 File.Open() 。它允许您指定其他参数以避免锁定。我认为它应该奏效。

    例如,你可以 var stream = new FileStream(textfile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

        2
  •  3
  •   Andy    3 年前

    首先,如果你的应用程序是多线程的,你不应该使用 bool 警卫。您应该使用线程同步工具,如锁、互斥、事件和/或信号量。

    此外,你的阅读是开放分享的,但你的写作不是。

    您也没有将流包裹进来 using 阻碍。这是另一个问题。你永远不应该这样做:

    StreamWriter sw = new StreamWriter(fs);
    

    您应该这样做:

    using(var sw = new StreamWriter(fs))
    {
        // ...
    }
    

    实现对象的基本规则 Dispose 你应该总是把它们包起来 使用 块。

    除此之外,你可能不想边写边读。这将给你带来巨大的竞争条件问题,当你需要调试正在发生的事情时,这些问题将很难重现。

    既然你没有使用async/await,我建议使用锁。这将一次只允许一个线程执行文件操作。没有种族条件,没有“共享”文件。

    private static readonly object _fileLock = new object();
    
    public static void RemoveCoinFromBuyOrderLogs(string symbol)
    {
        lock(_fileLock)
        {
            var newlines = File.ReadAllLines(walletFilename)
                .Where(c =>
                    !c.StartsWith(symbol + "USDT") &&
                    !c.StartsWith(symbol + "BUSD") &&
                    !c.StartsWith(symbol + "USDC") &&
                    !c.StartsWith(symbol + "TUSD"));
    
            File.WriteAllLines(walletFilename, newlines);
        }
    }
    
    public static void AddCoinToOrderLogs(string newOrder, long orderId)
    {
        lock (_fileLock)
        {
            var lines = File.ReadAllLines(walletFilename).ToList();
            lines = lines.Select(line => line.Replace("\r", "")).ToList();
            lines = lines.Where(line => line != "").Select(line => line).ToList();
    
            var fields = lines.Select(line => line.Split('\t')).ToList();
    
            bool duplicate = false;
            foreach (var field in fields)
            {
                if (field.Length >= 5)
                {
                    long id = Convert.ToInt64(field[4]);
                    if (id == orderId)
                        duplicate = true;
                }
            }
    
            if (!duplicate)
            {
                lines.Add(newOrder);
                lines.Sort();
                File.WriteAllLines(walletFilename, lines);
            }
        }
    }
    

    我无法测试这段代码,因为我没有要测试的数据,但请尝试让它看起来接近这一点。

    而且,老实说,我认为你应该使用像SQLite数据库这样的东西来做这类工作。用多个线程操作单个平面文件是一件很难正确有效地完成的事情。

    ETA

    下面是一个使用async/await模式的示例 SemaphoreSlim 用于同步

    private static readonly SemaphoreSlim _smph = new SemaphoreSlim(1, 1);
    
    private static async Task<IEnumerable<string>> ReadAllLinesAsync(
        string fileName, bool removeEmptyLines = true)
    {
        using (var s = File.OpenText(fileName))
        {
            var data = await s.ReadToEndAsync().ConfigureAwait(false);
            return await Task.Run(() =>
                data.Split(new[] { Environment.NewLine },
                    removeEmptyLines ? StringSplitOptions.RemoveEmptyEntries : StringSplitOptions.None));
        }
    }
    
    private static async Task WriteAllLinesAsync(string fileName, IEnumerable<string> lines)
    {
        using (var s = File.OpenWrite(fileName))
        using (var sr = new StreamWriter(s))
        {
            var data = await Task.Run(() => 
                string.Join(Environment.NewLine, lines)).ConfigureAwait(false);
            await sr.WriteAsync(data);
        }
    }
    
    public static async Task RemoveCoinFromBuyOrderLogsAsync(string symbol)
    {
        await _smph.WaitAsync().ConfigureAwait(false);
        try
        {
            var lines = await ReadAllLinesAsync(walletFilename);
            lines = lines.Where(c =>
                    !c.StartsWith(symbol + "USDT") &&
                    !c.StartsWith(symbol + "BUSD") &&
                    !c.StartsWith(symbol + "USDC") &&
                    !c.StartsWith(symbol + "TUSD"));
            await WriteAllLinesAsync(walletFilename, lines);
        }
        finally
        {
            _smph.Release();
        }
    }
    
    public static async Task AddCoinToOrderLogsAsync(string newOrder, long orderId)
    {
        await _smph.WaitAsync().ConfigureAwait(false);
        try
        {
            var lines = await ReadAllLinesAsync(walletFilename);
    
            var duplicate = lines.Select(line => line.Split('\t'))
                    .Any(x => (x.Length >= 5) && Convert.ToInt64(x[4]) == orderId);
    
            if (!duplicate)
            {
                var newLines = await Task.Run(() =>
                {
                    var newList = lines.ToList();
                    newList.Add(newOrder);
                    newList.Sort();
                    return newList;
                });
    
                await WriteAllLinesAsync(walletFilename, newLines);
            }
        }
        finally
        {
            _smph.Release();
        }
    }
    

    我补充道 Task.Run 在我想的部分 能够 是CPU密集型操作。

        3
  •  1
  •   redParrot    3 年前
    File.ReadAllLines(StringPath)
    

    此函数打开一个文本文件,读取文件的所有行,然后关闭文件。 如果文件小于4G,可能会有用。

    将结果字符串数组转换为列表,并对其执行任何您想要的操作。文件保持自由和解锁状态。 使用更新最终结果 WriteAllLines(path,StringLines) 最后。

    如果真的需要实时执行,也有异步方法 This article 这可能真的很有帮助。这些功能是

    阅读

    public async Task SimpleReadAsync()
    {
        string filePath = "simple.txt";
        string text = await File.ReadAllTextAsync(filePath);
    
        Console.WriteLine(text);
    }
    

    用于写作

        public async Task SimpleWriteAsync()
        {
            string filePath = "simple.txt";
            string text = $"Hello World";
        await File.WriteAllTextAsync(filePath, text);
    
    }