Create 2 FileStream on the same file in the same p

2019-06-20 08:43发布

问题:

I'm trying to create a temporary file that will be automatically deleted.

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

This file will be used by a 3rd party API which will also create a FileStream:

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

I think I've tried all possible combination of flags but I always get a "The process cannot access the file 'XXX' because it is being used by another process..."

Am I doing something wrong? Is there a way around?

回答1:

According to the documentation, yes.

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

Excerpt:

Read: Allows subsequent opening of the file for reading. If this flag is not specified, any request to open the file for reading (by this process or another process) will fail until the file is closed. However, even if this flag is specified, additional permissions might still be needed to access the file.



回答2:

I have exactly the same use case and encounter the same problem. What I try is using (FileShare.ReadWrite | FileShare.Delete) for both streams and it works.



回答3:

It sounds as if you may want to use a memory mapped file as a method to share a single file with multiple processes.

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



回答4:

The problem is that you still have the first stream you created open. You need to create the file, then release it (close stream), then have the 3rd party API do it's work, then delete the file. Wrapping all this up in a class that is IDispoable might be a nice solution; create and release the file in the contructor, method wrap the 3rd party work, delete in the dispose method.



回答5:

You can pass existing stream to 3-rd party Api, or if you want only read only mode for 3-rd party Api pass StreamReader instance

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


回答6:

This sequence of calls will only work if the third party API uses FileShare.ReadWrite, or your open uses FileAccess.Read.

You are opening it read/write, while allowing others to also open it read/write. The third-party code is trying to open it read-only, while allowing others to also have it open, but only as read-only. Since you still have it open read-write, this fails.

Assuming that you can't change the third-party code, you will need to adopt the following pattern instead:

  1. Open the file as you currently are, but without the DeleteOnClose flag.
  2. Write any content that you need the other code to read.
  3. Close the file.
  4. Optionally reopen it with FileAccess.Read (and possibly DeleteOnClose).
  5. Call the third party code.
  6. Do any other reading (but not writing) that you want.


回答7:

In my experience, a FileStream opened with FileOptions.DeleteOnClose cannot be opened by passing the file path to another FileStream regardless of the FileShare value.

When you own all the code (clearly not your case, sorry) DuplicateHandle can be used to open a DeleteOnClose file multiple times, even from different processes.

Here's some example code for .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());
      }
    }
  }
}