.NET
file handling
concurrency
file locking
programming tutorial

Wait until file is unlocked in .NET

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In .NET, waiting for a file to become unlocked is usually really a question of how to acquire the file handle safely when another process might still be using it. The important design point is that a separate “is it unlocked yet” check is race-prone, so the safest pattern is usually to retry the real open operation and keep the handle you successfully obtain.

Avoid a Separate Check Step

A method such as IsFileUnlocked(path) looks attractive, but it creates a race. Even if the check says the file is free, another process can grab the file before your next line opens it.

That is why the better abstraction is not “wait until unlocked.” It is “open when available.” If the open succeeds, you already own the handle that matters.

Retry the Real Open

The usual .NET signal for lock contention is an IOException when creating a FileStream. A bounded retry loop is a practical solution.

csharp
1using System;
2using System.IO;
3using System.Threading;
4using System.Threading.Tasks;
5
6public static class FileAccessHelper
7{
8    public static async Task<FileStream> OpenWhenAvailableAsync(
9        string path,
10        FileMode mode,
11        FileAccess access,
12        TimeSpan timeout,
13        CancellationToken cancellationToken = default)
14    {
15        DateTime deadline = DateTime.UtcNow + timeout;
16
17        while (true)
18        {
19            cancellationToken.ThrowIfCancellationRequested();
20
21            try
22            {
23                return new FileStream(path, mode, access, FileShare.None);
24            }
25            catch (IOException) when (DateTime.UtcNow < deadline)
26            {
27                await Task.Delay(100, cancellationToken);
28            }
29        }
30    }
31}

The key feature is that the method returns the FileStream itself. That means the successful open and the later work are the same operation.

Use the Acquired Handle

Once the helper returns a stream, do the work through that stream instead of reopening the file later.

csharp
1using System.IO;
2using System.Text;
3using System.Threading.Tasks;
4
5FileStream stream = await FileAccessHelper.OpenWhenAvailableAsync(
6    path: "output.txt",
7    mode: FileMode.OpenOrCreate,
8    access: FileAccess.Write,
9    timeout: TimeSpan.FromSeconds(5));
10
11await using (stream)
12{
13    byte[] bytes = Encoding.UTF8.GetBytes("completed\n");
14    stream.Seek(0, SeekOrigin.End);
15    await stream.WriteAsync(bytes, 0, bytes.Length);
16}

If you reopen the file after the helper returns, you bring the race back.

Timeouts and Cancellation Matter

If another process never releases the file, waiting forever is usually the wrong outcome. A timeout is part of the API contract, not an optional extra.

Cancellation matters for the same reason. If the calling workflow is shutting down or the user has cancelled the operation, the retry loop should stop promptly instead of continuing to poll pointlessly.

This makes the helper usable in long-running services and UI applications, not just in throwaway scripts.

Not Every IOException Is a Lock Problem

One subtle point is that IOException can happen for reasons other than file locks. The path might not exist yet, a directory could be missing, or the process might not have permission to access the file.

If your workflow needs better diagnostics, narrow the conditions you retry or log the exact context of the failure. A blind “retry all I O errors forever” loop can hide real bugs.

Sometimes Waiting Is the Wrong Design

If your application regularly waits on another process to finish writing files, the file lock may be the wrong coordination mechanism. In many systems, it is cleaner to:

  • write to a temporary file and rename when complete
  • create a separate “ready” marker file
  • coordinate through an application-level queue or protocol

Those designs are often easier to reason about than using lock contention as normal control flow.

Scope Your Own File Handles Correctly

A surprising number of “wait until unlocked” problems come from your own code forgetting to dispose a stream. If you opened the file earlier and did not close it promptly, no amount of retry logic fixes the underlying ownership bug.

That is why using and await using are still the first tools to reach for. Retry logic should handle external contention, not compensate for sloppy stream lifetime management.

Common Pitfalls

  • Checking whether the file is unlocked and then reopening it later, which creates a race.
  • Retrying forever without a timeout or cancellation path.
  • Catching every IOException and assuming the only cause is a lock.
  • Discarding the successfully opened handle and reopening the file again.
  • Using lock contention as an application protocol when a ready signal would be clearer.

Summary

  • In .NET, the safest pattern is usually “open when available,” not “check if unlocked.”
  • Retry the actual FileStream open with a bounded delay and timeout.
  • Keep and use the handle you successfully acquired.
  • Add cancellation and meaningful diagnostics so failures are debuggable.
  • If waiting is routine, consider redesigning the workflow instead of treating file locks as normal coordination.

Course illustration
Course illustration

All Rights Reserved.