打开文件并通过超时异步获取 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 库中发生的事情。
WindowsCreateFile
API中的一个基本问题,实际文件打开是运行同步的,没有超时。
确实存在 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);
}
}
对于现实世界的上下文,我正在尝试解决构建 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 库中发生的事情。
WindowsCreateFile
API中的一个基本问题,实际文件打开是运行同步的,没有超时。
确实存在 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);
}
}