【问题标题】:Create 2 FileStream on the same file in the same process在同一个进程中对同一个文件创建2个FileStream
【发布时间】:2011-11-21 15:52:55
【问题描述】:

我正在尝试创建一个将自动删除的临时文件。

stream = new FileStream(
           tmpFilePath, 
           FileMode.OpenOrCreate, 
           FileAccess.ReadWrite, 
           FileShare.ReadWrite, 
           4096, 
           FileOptions.DeleteOnClose|FileOptions.RandomAccess
           );

此文件将由第三方 API 使用,该 API 还将创建 FileStream:

stream = new FileStream(
          tmpFilePath, 
          FileMode.Open, 
          FileAccess.Read, 
          FileShare.Read);

我想我已经尝试了所有可能的标志组合,但我总是收到“该进程无法访问文件 'XXX',因为它正在被另一个进程使用......”

我做错了吗?有办法吗?

【问题讨论】:

  • 不能把文件流分享到第三方库吗?
  • 先创建临时文件,然后关闭流。然后第 3 方 API 将能够访问它。完成工作后,删除临时文件。如果需要,使用同步。
  • 我猜你不能在另一个进程中使用FileAccess.Read, FileShare.Read,试试FileAccess.ReadWrite, FileShare.ReadWrite
  • 我无法关闭流,因为我使用 FileOptions.DeleteOnClose 标志创建了它。有了这个标志,我确信即使进程崩溃,文件也会被删除。关闭流将删除文件。这就是为什么我想保持它打开,直到 API 完成它的工作,然后只关闭流

标签: c# .net filestream file-sharing


【解决方案1】:

根据文档,是的。

http://msdn.microsoft.com/en-us/library/system.io.fileshare.aspx

摘录:

读取:允许随后打开文件进行读取。如果未指定此标志,则任何打开文件进行读取的请求(此进程 或其他进程)都将失败,直到文件关闭。但是,即使指定了此标志,仍可能需要其他权限才能访问该文件。

【讨论】:

    【解决方案2】:

    我有完全相同的用例并遇到同样的问题。我尝试对两个流使用 (FileShare.ReadWrite | FileShare.Delete) 并且它有效。

    【讨论】:

    • 肯在正确的轨道上。如果打开一个 FileStream 进行写入并指定 FileShare.Read,然后打开第二个 FileStream 进行读取,则它必须指定 FileShare.ReadWrite 以允许与“写入”文件流(例如第一个 FileStream)共享。
    【解决方案3】:

    根据我的经验,使用FileOptions.DeleteOnClose 打开的FileStream 无法通过将文件路径传递给另一个FileStream 来打开,无论FileShare 的值如何。

    如果您拥有所有代码(显然不是您的情况,抱歉)DuplicateHandle 可用于多次打开 DeleteOnClose 文件,即使来自不同的进程。

    这是 .NET 4.5.1 的一些示例代码。

    using System;
    using System.Diagnostics;
    using System.IO;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Windows.Forms;
    using Microsoft.Win32.SafeHandles;
    
    namespace Example
    {
      public static class DuplicatedHandleExample
      {
        [DllImport("kernel32.dll")]
        private static extern bool DuplicateHandle(
          SafeFileHandle hSourceProcessHandle,
          IntPtr hSourceHandle,
          SafeFileHandle hTargetProcessHandle,
          out SafeFileHandle lpTargetHandle,
          UInt32 dwDesiredAccess,
          bool bInheritHandle,
          UInt32 dwOptions);
    
        [DllImport("kernel32.dll")]
        private static extern SafeFileHandle OpenProcess(
          UInt32 dwDesiredAccess,
          bool bInheritHandle,
          int dwProcessId);
    
        private const UInt32 PROCESS_DUP_HANDLE = 0x0040;
    
        private const UInt32 DUPLICATE_SAME_ACCESS = 0x0002;
    
        public static void CreateFileInProcessA()
        {
          try
          {
            // open new temp file with FileOptions.DeleteOnClose
            string tempFilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("D"));
            using (FileStream fs = new FileStream(tempFilePath, FileMode.CreateNew,
              FileAccess.ReadWrite, FileShare.Read | FileShare.Write | FileShare.Delete,
              4096, FileOptions.DeleteOnClose))
            {
              // put a message in the temp file
              fs.Write(new[] { (byte)'h', (byte)'i', (byte)'!' }, 0, 3);
              fs.Flush();
    
              // put our process ID and file handle on clipboard
              string data = string.Join(",",
                Process.GetCurrentProcess().Id.ToString(),
                fs.SafeFileHandle.DangerousGetHandle().ToString());
    
              Clipboard.SetData(DataFormats.UnicodeText, data);
    
              // show messagebox (while holding file open!) and wait for user to click OK
              MessageBox.Show("Temp File opened. Process ID and File Handle copied to clipboard. Click OK to close temp file.");
            }
          }
          catch (Exception ex)
          {
            MessageBox.Show(ex.ToString());
          }
        }
    
        public static void OpenFileInProcessB()
        {
          try
          {
            // get process ID and file handle from clipboard
            string data = (string)Clipboard.GetData(DataFormats.UnicodeText);
            string[] dataParts = data.Split(',');
            int sourceProcessId = int.Parse(dataParts[0]);
            IntPtr sourceFileHandle = new IntPtr(Int64.Parse(dataParts[1]));
    
            // get handle to target process
            using (SafeFileHandle sourceProcessHandle =
              OpenProcess(PROCESS_DUP_HANDLE, false, sourceProcessId))
            {
              // get handle to our process
              using (SafeFileHandle destinationProcessHandle =
                OpenProcess(PROCESS_DUP_HANDLE, false, Process.GetCurrentProcess().Id))
              {
                // duplicate handle into our process
                SafeFileHandle destinationFileHandle;
                DuplicateHandle(sourceProcessHandle, sourceFileHandle,
                  destinationProcessHandle, out destinationFileHandle,
                  0, false, DUPLICATE_SAME_ACCESS);
    
                // get a FileStream wrapper around it
                using (FileStream fs = new FileStream(destinationFileHandle, FileAccess.ReadWrite, 4096))
                {
                  // read file contents
                  fs.Position = 0;
                  byte[] buffer = new byte[100];
                  int numBytes = fs.Read(buffer, 0, 100);
                  string message = Encoding.ASCII.GetString(buffer, 0, numBytes);
    
                  // show messagebox (while holding file open!) and wait for user to click OK
                  MessageBox.Show("Found this message in file: " + message + Environment.NewLine +
                    "Click OK to close temp file");
                }
              }
            }
          }
          catch (Exception ex)
          {
            MessageBox.Show(ex.ToString());
          }
        }
      }
    }
    

    【讨论】:

      【解决方案4】:

      听起来您可能想使用内存映射文件作为与多个进程共享单个文件的方法。

      http://msdn.microsoft.com/en-us/library/system.io.memorymappedfiles.memorymappedfile.aspx

      【讨论】:

        【解决方案5】:

        问题是您创建的第一个流仍然处于打开状态。您需要创建文件,然后释放它(关闭流),然后让第 3 方 API 完成它的工作,然后删除文件。将所有这些封装在一个 IDispoable 类中可能是一个不错的解决方案;在contructor中创建和释放文件,方法包裹第3方工作,在dispose方法中删除。

        【讨论】:

        • 他有问题打开流,没有注意到它。
        • 我无法关闭流,因为我使用 FileOptions.DeleteOnClose 标志创建了它。有了这个标志,我确信即使进程崩溃,文件也会被删除。关闭流将删除文件。这就是为什么我想让它保持打开状态,直到 API 完成它的工作,然后只关闭流
        【解决方案6】:

        您可以将现有流传递给 3-rd 方 Api,或者如果您想要 3-rd 方 Api 的只读模式传递 StreamReader 实例

            using (var stream = new FileStream("trace.txt", FileMode.OpenOrCreate,FileAccess.ReadWrite))
            {
                using (var anotherStream = new StreamReader(stream))
                {
                    //magic here
                }
            }
        

        【讨论】:

          【解决方案7】:

          仅当第三方 API 使用 FileShare.ReadWrite,或者您的开放使用 FileAccess.Read 时,此调用序列才有效。

          您正在以读/写方式打开它,同时允许其他人也以读/写方式打开它。 第三方代码试图以只读方式打开它,同时允许其他人也将其打开,但只能以只读方式打开。由于您仍然将其打开读写,因此失败。

          假设您无法更改第三方代码,则需要采用以下模式:

          1. 按当前方式打开文件,但不带DeleteOnClose 标志。
          2. 编写需要其他代码读取的任何内容。
          3. 关闭文件。
          4. 可选择使用FileAccess.Read(也可能使用DeleteOnClose)重新打开它。
          5. 调用第三方代码。
          6. 做任何其他你想做的阅读(但不是写作)。

          【讨论】:

            猜你喜欢
            • 2017-05-24
            • 2011-06-28
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-11-15
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多