没有流打开时在路径上共享冲突

Sharing Violation on Path when no stream is open

我正在用 C# 开发一款游戏,其中每个 "District" 都会定期将有关它的数据存储在保存文件中。为了测试,"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 方法不使用流,因此我看不出如何保持打开状态。其实关卡到这一步,阅读流根本就没有打开

我是不是漏掉了什么明显的东西?

信息不足,无法确定,但如果您查看此代码:

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 块而不是对 open/close 文件的单独方法调用会更好。

你说第28、29行抛出异常

第 29 行

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

对于第 29 行,我可以很容易地看到发生了什么。您未能在 File.Create(). File.Create(path) is a shorthand for new FileStream(path, FileMode.Create). Since it is unspecified, the default value of FileShare.None 返回的 FileStream 上调用 .Dispose()。这意味着对 File.WriteAllText() 的后续调用将失败。

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

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

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

第 28 行

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

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

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

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

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

建议

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

我注意到关于您的代码的一件事是,您甚至从未利用 saveFileReader 是一个字段这一事实。您可以像使用本地变量一样使用该变量。它是一个字段而不是本地字段这一事实向代码的任何 reader 建议您从多个方法引用它或打算在其中存储一个值,该值必须在多个调用中保留。例如,如果您首先要费心将它设为一个字段,那么从 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,则更容易编写正确的代码。此外,这种技术提高了可读性,因为您可以知道变量只能从方法内部访问,而不可能被同一 class.

中的任何其他方法访问。