【问题标题】:Sharing Violation on Path when no stream is open没有打开流时共享路径上的冲突
【发布时间】:2015-09-03 21:50:20
【问题描述】:

我正在用 C# 开发一个游戏,其中每个“地区”都会定期将有关它的数据存储在保存文件中。为了测试,“SaveAll”方法在关卡开始时被调用一次。

文件操作代码如下:

using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.IO;

public class DistrictSaveData : KeepAwake {

    private string saveDirectoryPath = string.Concat(
        System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments),
        "\\My Games\\District\\Districts");
    private string saveFilePath = string.Concat(
        System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments),
        "\\My Games\\District\\Districts\\District-x.dat");
    private StreamReader saveFileReader;

    public void SaveAll() {
        foreach (GameObject gO in GameObject.FindGameObjectsWithTag("District")) {
            District district = gO.GetComponent<District>();
            saveFilePath = string.Concat(
                System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments),
                "\\My Games\\District\\Districts\\District-", district.id , ".dat");
            if (!Directory.Exists(saveDirectoryPath)) {
                Directory.CreateDirectory(saveDirectoryPath);
            }
            try {
            File.Delete(saveFilePath);
            } catch {}
            File.Create(saveFilePath);
            File.WriteAllText(saveFilePath, district.SendSaveData());
        }
    }

    public void LoadAll() {
        foreach (GameObject gO in GameObject.FindGameObjectsWithTag("District")) {
            District district = gO.GetComponent<District>();
            saveFilePath = string.Concat(
                System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments),
                "\\My Games\\District\\Districts\\District-", district.id , ".dat");
            if(File.Exists(saveFilePath)) {
                OpenFileForReading();
                district.isHQ = bool.Parse(saveFileReader.ReadLine());
                district.controllingFaction = StringToFaction(saveFileReader.ReadLine());
                district.agricultureSpecialisation = StringToAgricultureSpecialisation(saveFileReader.ReadLine());
                district.technologySpecialisation = StringToTechnologySpecialisation(saveFileReader.ReadLine());
                district.militarySpecialisation = StringToMilitarySpecialisation(saveFileReader.ReadLine());
                CloseFileAfterReading();
            } else
                break;
        }
    }

    /// <summary>
    /// Opens the save file for reading.
    /// </summary>
    private void OpenFileForReading() {
        saveFileReader = File.OpenText(saveFilePath);
    }

    /// <summary>
    /// Closes the save file after reading.
    /// </summary>
    private void CloseFileAfterReading() {
        saveFileReader.Close();
    }

    private Faction StringToFaction(string stringToConvert) {
        switch (stringToConvert) {
        case "TheCrimsonLegion":
            return Faction.TheCrimsonLegion;
        case "TheVanguardsOfChaos":
            return Faction.TheVanguardsOfChaos;
        case "TheEmeraldFoxes":
            return Faction.TheEmeraldFoxes;
        case "TheSyndicate":
            return Faction.TheSyndicate;
        case "TheKeepersOfTheTome":
            return Faction.TheKeepersOfTheTome;
        case "TheArchitectsOfThought":
            return Faction.TheArchitectsOfThought;
        default:
            return Faction.None;
        }
    }

    private AgricultureSpecialisation StringToAgricultureSpecialisation(string stringToConvert) {
        switch (stringToConvert) {
        case "Farm":
            return AgricultureSpecialisation.Farm;
        case "Plantation":
            return AgricultureSpecialisation.Plantation;
        case "Biodome":
            return AgricultureSpecialisation.Biodome;
        default:
            return AgricultureSpecialisation.None;
        }
    }

    private TechnologySpecialisation StringToTechnologySpecialisation(string stringToConvert) {
        switch (stringToConvert) {
        case "Laboratory":
            return TechnologySpecialisation.Laboratory;
        case "University":
            return TechnologySpecialisation.University;
        case "GreatTechnologicalInstitution":
            return TechnologySpecialisation.GreatTechnologicalInstitution;
        default:
            return TechnologySpecialisation.None;
        }
    }

    private MilitarySpecialisation StringToMilitarySpecialisation(string stringToConvert) {
        switch (stringToConvert) {
        case "Outpost":
            return MilitarySpecialisation.Outpost;
        case "Barracks":
            return MilitarySpecialisation.Barracks;
        case "Fortress":
            return MilitarySpecialisation.Fortress;
        default:
            return MilitarySpecialisation.None;
        }
    }
}

抛出的异常(多次)如下:

IOException: Sharing violation on path C:\users\samdy1\My Documents\My Games\District\Districts\District-0.dat
System.IO.FileStream..ctor (System.String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, Boolean anonymous, FileOptions options) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.IO/FileStream.cs:320)
System.IO.FileStream..ctor (System.String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize)
(wrapper remoting-invoke-with-check) System.IO.FileStream:.ctor (string,System.IO.FileMode,System.IO.FileAccess,System.IO.FileShare,int)
System.IO.File.Create (System.String path, Int32 bufferSize) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.IO/File.cs:135)
System.IO.File.Create (System.String path) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.IO/File.cs:130)
DistrictSaveData+<SaveAll>c__Iterator3.MoveNext () (at Assets/Scripts/_Core/SaveData/DistrictSaveData.cs:29)

在 SaveAll 方法中进程的第 28 行和第 29 行抛出此异常。但是,SaveAll 方法不使用流,所以我看不到如何保持打开状态。事实上,此时关卡中,读取流根本没有打开。

我是否遗漏了一些明显的东西?

【问题讨论】:

  • 旁注:考虑Path.Combine 而不是string.Concat。它会自动确保正确处理路径分隔符。
  • 那些文件系统阅读器等是IDisposable。我认为与其将实例作为成员,不如应用using reader = new FileReader(path) { ... } 方法。
  • 原因是,如果在读取文件时抛出异常,您不会关闭它们。
  • 第二个原因是,如果您碰巧有多个线程处理不同的文件,那么您需要担心的事情也会更少。
  • @EricJ。此时尚未调用 LoadAll() 中的代码,因此读取它们不会引发异常。无论如何,我尝试在导致问题的 File.Create() 和 File.WriteAllText() 行之前关闭文件,但仍然出现相同的错误。

标签: c# io system.io.file sharing-violation


【解决方案1】:

没有足够的信息可以确定,但是如果你看一下这段代码:

OpenFileForReading();
district.isHQ = bool.Parse(saveFileReader.ReadLine());
district.controllingFaction = StringToFaction(saveFileReader.ReadLine());
district.agricultureSpecialisation = StringToAgricultureSpecialisation(saveFileReader.ReadLine());
district.technologySpecialisation = StringToTechnologySpecialisation(saveFileReader.ReadLine());
district.militarySpecialisation = StringToMilitarySpecialisation(saveFileReader.ReadLine());
CloseFileAfterReading();

如果在 OpenFileForReading() 之后但在 CloseFileAfterReading() 之前抛出异常,则不会关闭文件,您将看到您描述的行为。

至少将代码重写为

OpenFileForReading();
try
{
    district.isHQ = bool.Parse(saveFileReader.ReadLine());
    district.controllingFaction = StringToFaction(saveFileReader.ReadLine());
    district.agricultureSpecialisation = StringToAgricultureSpecialisation(saveFileReader.ReadLine());
    district.technologySpecialisation = StringToTechnologySpecialisation(saveFileReader.ReadLine());
    district.militarySpecialisation = StringToMilitarySpecialisation(saveFileReader.ReadLine());
}
finally
{
    CloseFileAfterReading();
}

使用using 块而不是单独的方法调用来打开/关闭文件会更好。

【讨论】:

  • +1 用于将相同的代码部分识别为可能的候选者,这也引起了我的注意。这种 C 风格的方法(打开、使用、关闭)不如 using 块可靠。
【解决方案2】:

你说异常是在第 28 行和第 29 行抛出的。

第 29 行

这一行指的是File.WriteAllText() 调用。

对于第 29 行,我可以很容易地看到发生了什么。您未能在File.Create() 返回的FileStream 上调用.Dispose()File.Create(path)new FileStream(path, FileMode.Create) 的简写。由于未指定,因此使用默认值FileShare.None。这意味着随后对File.WriteAllText() 的调用将失败。

您应该删除File.Create(),而不是这种模式。 File.WriteAllText() 已经创建或截断文件,因此无需预先创建或手动截断它。如果你坚持调用File.Create()(这是无意义的),你应该像下面这样在using中调用它,以确保在调用File.WriteAllText()之前它的句柄是关闭的:

using (File.Create(saveFilePath)) {
}
File.WriteAllText(saveFilePath, district.SendSaveData());

垃圾回收可能发生在File.Create() 返回之后和File.WriteAllText() 尝试打开文件之前。例如,如果district.SendSaveData() 创建了大量对象并使用大量内存,则可能会发生这种情况。这种行为可能(但不保证)导致垃圾收集发生。此外,方法的所有参数始终在实际方法调用之前进行评估,因此File.WriteAllText() 在该方法退出之前不会运行。如果在此时间间隔内确实发生了垃圾回收,则从File.Create() 返回的FileStream 的终结器可能在File.WriteAllText() 打开文件之前运行。如果发生这种情况,那么您将不会在第 29 行看到异常。

第 28 行

这一行指的是File.Create() 调用。

对于第 28 行发生的异常,我只能猜测发生了什么。从您的代码中不清楚将调用您的类的公共方法的顺序。但是,您想到了一种可能性。

如果您从省略的代码中调用 SaveAll() 捕获了第 29 行的异常,然后稍后再次调用 SaveAll() 而没有运行垃圾收集和终结器,则第 28 行应该抛出共享冲突异常。这是事件的顺序:

  1. 第 28 行第一次针对特定地区运行,并返回 FileStream。让我们将这个人命名为 FileStreamA。
  2. 由于FileStreamA 持有相关文件的打开文件句柄,第 29 行引发异常。
  3. 第 29 行的异常被外部代码捕获。
  4. 外部代码再次调用SaveAll()
  5. 第 28 行尝试再次为同一地区打开第二个 FileStream,并由于 FileStream A 仍然持有相关文件的打开文件句柄而引发异常。
  6. 最终垃圾收集器运行并调用 FileStreamA 的终结器。发生这种情况后,该区域的代码将超过第 28 行(但可能会在第 29 行失败)。

另一种可能性是文件正在任何其他进程中打开或由程序的其他部分打开。

建议

我不认为这与错误有关,但我必须提及。我强烈建议不要像使用saveFileReader 那样使用字段变量来保存TextReader。这将范围与变量的生命周期分离。相反,您应该将TextReader 作为参数传递给方法,使用using 控制其生命周期,并避免将其保存到不受范围规则控制的变量(例如字段),除非您正在制作便利对象它本身支持范围规则(例如,通过实现IDisposable)。

我注意到您的代码的一件事是您甚至从未利用saveFileReader 是一个字段这一事实。您使用该变量,就像它是本地变量一样。它是一个字段而不是本地的这一事实向代码的任何读者表明,您可以从多个方法中引用它,或者打算在其中存储一个必须在多个调用中保留的值。例如,如果您打算首先将其设置为字段,那么从 StringToAgricultureSpecialisation() 访问该变量是有意义的。但是,您只能使用 OpenFileForReading()CloseFileForReading() 将其作为字段访问。

我将仅演示如何在您的代码中使用范围模式:

using (var saveFileReader = File.OpenText(saveFilePath)) {
    district.isHQ = bool.Parse(saveFileReader.ReadLine());
    district.controllingFaction = StringToFaction(saveFileReader.ReadLine());
    district.agricultureSpecialisation = StringToAgricultureSpecialisation(saveFileReader.ReadLine());
    district.technologySpecialisation = StringToTechnologySpecialisation(saveFileReader.ReadLine());
    district.militarySpecialisation = StringToMilitarySpecialisation(saveFileReader.ReadLine());
}

看到了吗?不需要字段。

通常,如果可能,如果字段或属性引用具有基于范围的生命周期的对象,则应避免使用它们,因为使用字段或属性时更容易出错。如果您可以改用局部变量和using,则更容易编写正确的代码。此外,这种技术提高了可读性,因为您可以知道该变量只能从方法内部访问,而不可能被同一类中的任何其他方法访问。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多