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

时间:2015-09-03 21:50:20

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

我正在开发一个C#游戏,其中每个"区"将定期存储在保存文件中的数据。为了测试," SaveAll"方法在关卡的开头调用一次。

文件操作的代码如下:

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)

抛出的异常(多次)读取:

CREATE VIEW [dbo].[ItemWarehouseStockForecastDaily2]
AS    
SELECT
    fd.AsafterDate
    , iw.idItem
    , iw.idWarehouse
    , iw.OnHandQuantity  
        + SUM(ISNULL(iwio.PurchaseOrderInboundQuantity, 0)
                - iws.AverageMonthlyDemandQuantity / (365.25/12)
            ) OVER (ORDER BY fd.AsafterDate) AS OnHandQuantity
FROM
    (
        ( SELECT CalendarDate Asafterdate 
          FROM Calendar c 
          WHERE c.CalendarDate > GETDATE() 
            AND c.CalendarDate < DATEADD(d, 180, GETDATE())
        ) fd -- This table has 180 rows
        -- This table has 10 million rows - one per item per warehouse
        CROSS JOIN ItemWarehouse iw 
    )
    LEFT JOIN ItemWarehouseDemandFromStockStatisticsMonthly iws
           ON iws.idItem = iw.idItem 
          AND iws.idWarehouse = iw.idWarehouse
    LEFT JOIN ItemWarehouseInboundAndOutboundQuantitiesWithDueDate iwio 
           ON iwio.idItem = iw.idItem 
          AND iwio.idWarehouse = iw.idWarehouse 
          AND iwio.DueDate = fd.Asafterdate
/*
WHERE iw.idItem = 12345
  AND iw.idWarehouse = 67
ORDER BY AsafterDate
*/

在SaveAll方法的进程的第28和29行抛出此异常。但是,SaveAll方法不使用任何流,因此我无法看到如何保持打开状态。事实上,在关卡的这一点上,阅读流根本没有被打开过。

我错过了一些明显的东西吗?

2 个答案:

答案 0 :(得分:2)

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

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 :(得分:1)

你说在第28和29行引发了异常。

第29行

此行指的是File.WriteAllText()来电。

对于第29行,我可以很容易地看到发生了什么。您未能在File.Create()返回的.Dispose()上致电FileStreamFile.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()在该方法退出之前不会运行。如果在此间隔内发生垃圾收集,则FileStream返回的File.Create()的终结器可能会在File.WriteAllText()打开文件之前运行。如果发生这种情况,那么你就不会在第29行看到异常。

第28行

此行指的是File.Create()来电。

对于第28行发生的例外情况,我只能猜测发生了什么。从您的代码中不清楚您的类的公共方法将被调用哪个顺序。但是,有一种可能性会浮现在脑海中。

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

  1. 第28行第一次为特定区域运行并返回FileStream。让我们给这个人命名FileStream A。
  2. 第29行引发异常,原因是FileStream A持有相关文件的打开文件句柄。
  3. 第29行的异常被外部代码捕获。
  4. 外部代码再次调用SaveAll()
  5. 第28行尝试再次为同一个区域打开第二个FileStream,并因FileStream A仍然持有相关文件的打开文件句柄而引发异常。
  6. 最终垃圾收集器运行并调用FileStream A的终结器。在此之后,代码将超过该区域的第28行(但可能会在第29行失败)。
  7. 另一种可能性是文件在任何其他进程中打开或由程序的另一部分打开。

    建议

    我不相信这与错误有关,但我必须提到它。我强烈建议不要像使用TextReader一样使用字段变量来保存saveFileReader。这将范围与变量的生命周期区分开来。相反,您应该将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,则编写正确的代码会更容易。此外,这种技术提高了可读性,因为您可以知道变量只能从方法中访问,而不是可能被同一类中的任何其他方法访问。