UWP - 如果其他应用程序已经打开文件,就无法打开该文件进行阅读

3
2018年更新: 如下所示和其他来源所述,针对创意者更新或更高版本,允许只读访问已被另一个进程打开进行写入的文件。太好了!

我似乎在为桌面开发Windows Store应用程序时遇到了难题。 我试图打开另一个应用程序打开的大型(100+ MB)日志文件,并实时处理最新的事件,因为它们被写入文件。

使用常规的非沙盒化C#,这是相当简单的:

System.IO.FileStream stream = File.Open("LOGFILE PATH HERE", System.IO.FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

很遗憾,在UWP中,当我尝试打开被另一个应用程序使用的文件时,我会收到“UnauthorizedAccessException”错误。我尝试了所有我能找到的API和各种组合,但都没有成功,所以我来这里寻求一些建议。

以下是我尝试过的一些方法:

Windows.Storage.Pickers.FileOpenPicker picker = new Windows.Storage.Pickers.FileOpenPicker();
picker.ViewMode = Windows.Storage.Pickers.PickerViewMode.List;
//Prompt the user to open the log file:
Windows.Storage.StorageFile logFile = await picker.PickSingleFileAsync();
picker.FileTypeFilter.Add(".txt");

//This won't work in any case, because it doesn't use the handle that the user picked,
// so the UWP sandboxing blocks it:
new FileStream(logFile.Path, FileMode.OpenOrCreate, FileAccess.Read);

//EDIT: These don't work if the file is open either, I must have made a mistake earlier
await FileIO.ReadBufferAsync(logFile);
await FileIO.ReadLinesAsync(logFile);

//These work if the file is not open by another app, but fail if another app has the file open
await logFile.OpenAsync( Windows.Storage.FileAccessMode.Read);
await logFile.OpenStreamForReadAsync();

快速复现:

打开PowerShell窗口,并运行以下命令以在您的主目录中打开“test.txt”文件:

$f = [System.IO.File]::Open("test.txt", [System.IO.FileMode]::OpenOrCreate, [System.IO.FileAccess]::Write, [System.IO.FileShare]::ReadWrite);

请查看此帖子:https://dev59.com/TG855IYBdhLWcg3wViy3 - Sparrow
那只适用于非UWP应用程序,在这种情况下不起作用。这是我尝试的第一件事。此外,我在我的评论中将该代码作为未能成功的示例:*( - Debug Arnaut
这真让人沮丧...根据 System.Diagnostics.Stopwatch,即使在我的配备了 SSD 的超快开发机上,ReadBufferAsync 读取一个适度的 100MB 日志文件也需要 100 毫秒,所以我猜它确实是在缓存整个文件。 与此同时,在尚未打开的文件上,StorageFile 的 OpenAsync(...) 方法只需不到 2 毫秒,所以这正是我所需要的。该死! - Debug Arnaut
我不知道原因,但我认为如果其他应用程序已经写入了文件,那么您可以读取它,并且在Win32中当其他应用程序读取它时也可以读取它。 - lindexi
2个回答

3
这是作为周年更新(又称RS1)通用API的预期行为。Windows.Storage.*API和流使用所谓的“有礼貌的读者”模型。在此模型中,读者可能会被写者打断,从而生成OPLOCK中断错误。在RS1中,这也意味着如果已经存在任何打开的写句柄,则阻止读者。
在创作者更新(又称RS2)中,有些东西正在改变。随着通用平台从最初的WinRT单个前台应用程序进化,出现了允许应用程序使用更传统模型的需求。因此,在RS2中,我们正在进行一些更改以帮助这种情况。
  1. 如果写程序已经存在,则未修改的有礼貌的读取器将不再在打开时失败。但是,如果写入程序实际写入文件,则仍会发生OPLOCK中断。
  2. 共享违规直接显示给调用方而不是转换为AccessDenied。(为了兼容性,该新行为在应用程序清单中声明RS2作为测试平台后,才能启用)
  3. 提供了新的StorageOpenOptions,以便应用程序更改其代码以使用新选项,从而获得不涉及oplocks的行为,有效地选择退出OpLock行为。

1
我已经做了一个简单的测试,它应该可以工作。测试步骤如下: - 用记事本打开一个文件 file.txt,其中只包含一行文本, - 运行以下代码的应用程序, - 选择一个仍在记事本中打开的文件, - 你应该在调试输出中看到第一行和空的第二行。
代码如下:
public async Task GetFile()
{
    Windows.Storage.Pickers.FileOpenPicker picker = new Windows.Storage.Pickers.FileOpenPicker();
    picker.ViewMode = Windows.Storage.Pickers.PickerViewMode.List;
    picker.FileTypeFilter.Add(".txt");
    //Prompt the user to open the log file:
    Windows.Storage.StorageFile logFile = await picker.PickSingleFileAsync();

    try
    {
        using (var stream = await logFile.OpenStreamForReadAsync())
        using (var reader = new StreamReader(stream))
        {
            var line = await reader.ReadLineAsync();
            Debug.WriteLine($"The first line: {line} - waiting");
            await Task.Delay(10000);
            line = await reader.ReadLineAsync();
            Debug.WriteLine($"The next line: {line} - waiting");
        }
    }
    catch (Exception exc)
    {
        Debug.WriteLine($"Exception {exc.Message}");
    }
}

在第二个测试中,我已经在记事本中修改了文件并保存了它,而上面的代码则命中了await Task.Delay(),然后尝试读取第二行时,你可能会得到:'异常:与此 oplock 关联的句柄已关闭。oplock 现在已被破坏。'。
我发现你没有处理流,也许问题就出在这里?你是否尝试过使用using来处理Idisposable

谢谢Romasz。我尝试了OpenStreamForReadAsync,但它没有起作用。我刚刚尝试了您提供的示例,它确实适用于我在记事本中打开的文档,但如果我使用另一个应用程序(或PowerShell)保持它打开,它会抛出UnauthorizedAccessException。虽然我也是C#新手,但我应该如何执行该任务?我创建了一个按钮事件处理程序,只调用了“await GetFile()”。我将编辑我的原始帖子以添加我用于保持文件打开的powershell命令,这似乎复制了大型日志记录应用程序的行为。 - Debug Arnaut
@DebugArnaut,你尝试过其他应用程序或仅使用PowerShell吗?是独立的PowerShell还是在Visual Studio中? - Romasz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接