当没有流打开时,在路径上共享违规
本文关键字:路径 共享 | 更新日期: 2023-09-27 18:11:01
我正在用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方法不使用流,所以我不知道如何保持打开状态。事实上,在关卡的这一点上,阅读流根本没有打开。
我错过了什么明显的东西吗?
没有足够的信息可以确定,但是如果你看一下这段代码:
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
块而不是单独的方法调用来打开/关闭文件。
您说异常在第28行和第29行抛出。
第29行
本行为File.WriteAllText()
呼叫。
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行应该抛出共享冲突异常。以下是事件的顺序:
- 第28行第一次在特定区域运行并返回
FileStream
。我们把它命名为FileStream
a。 - 第29行抛出一个异常,因为
FileStream
a持有一个打开的文件句柄。 第29行的异常被外部代码捕获。 - 外部代码再次调用
SaveAll()
。 - 第28行试图再次为同一区域打开第二个
FileStream
,并抛出一个异常,因为FileStream
a仍然持有一个打开的文件句柄。 - 最终垃圾收集器运行并调用
FileStream
a的终结器。在此之后,该区域的代码将通过第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
,则更容易编写正确的代码。此外,这种技术增加了可读性,因为您可以知道变量只能从方法内部访问,而不可能被同一类中的任何其他方法访问。