打开文件并通过超时异步获取 FileStream

Open file and get FileStream asynchrounously with timeout

对于现实世界的上下文,我正在尝试解决构建 C# FileStream 的自动化过程中的一个有点罕见的问题,如下所示:

using (var file = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    ...

这个过程每天通过这一行数千次,在极少数情况下,无限期地挂在 FileStream 构造函数的某个地方。我怀疑挂起的原因可能是由于某些用户使用了在 Windows 内运行的备用文件系统进程作为指定路径内的服务,但无论如何,我希望能够通过异步打开 FileStream 并在合理的超时期限后中止来解决任何问题。我看到有同步 FileStream Read/Write 的文档,但我找不到任何用于初始获取打开文件的 FileStream 对象的信息。我已经尝试将 FileStream 打开包装在一个单独的线程以及一个异步任务中,并且我能够检测到该操作是否已挂起,这很好,但如果发生这种情况,我无法中止卡住的线程。特别是,.Net 不再支持 Thread.Abort,并且 CancellationToken 似乎也不适合我。所有 API 都希望 运行 线程正常终止,但我当然无法控制 .Net 库中发生的事情。

WindowsCreateFileAPI中的一个基本问题,实际文件打开是运行同步的,没有超时。

确实存在 CancelSynchronousIo,要使用它,您需要将实际的线程句柄传递给它,因此您需要 运行 在另一个 线程上打开文件,不是任务。我真的不会在生产代码中推荐这个。但是如果你想走这条路,你可以这样做:

(部分代码摘自 this answer,并针对 await 进行了修改)

public async static Task<FileStream> OpenFileAsync(string fileName, FileMode mode, int timeout)
// you could add more FileStream params here if you want.
{
    FileStream stream = null;
    uint threadId = 0;
    var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
    var thread = new Thread(() =>    //create a thread
        {
            try
            {
                Thread.BeginThreadAffinity();    // we need to lock onto a native thread
                Interlocked.Exchange(ref hThread, GetCurrentThreadId());
                Interlocked.Exchange(ref stream, new FileStream(fileName, mode));
                completion.SetResult();
            }
            catch(Exception ex)
            {
                completion.SetException(ex);
            }
            finally
            {
                Thread.EndThreadAffinity();
            }
        });
    thread.Start();

    if(await Task.WhenAny(completion.Task, Task.Delay(timeout)) == completion.Task)   //this returns false on timeout
    {
        await completion.Task; //unwrap exception
        return Interlocked.Read(ref stream);
    }

    // otherwise cancel the IO and throw
    CancelIo(Interlocked.Read(ref hThread));
    Interlocked.Read(ref stream)?.Dispose();
    throw new TimeoutException();
}

[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint GetCurrentThreadId();

[DllImport("kernel32.dll", SetLastError = true)]
private static extern SafeHandle OpenThread(uint desiredAccess, bool inheritHandle, uint threadId);

[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr handle);

[DllImport("Kernel32.dll", SetLastError = true)]
private static extern int CancelSynchronousIo(IntPtr threadHandle);

private static void CancelIo(uint threadId)
{
    var threadHandle = IntPtr.Zero
    try
    {
        threadHandle = OpenThread(0x1, false, threadId);     // THREAD_TERMINATE
        CancelSynchronousIo(threadHandle);
    }
    finally
    {
        if(threadHandle != IntPtr.Zero)
            CloseHandle(threadHandle);
    }
}