Question
I am writing a C# program that repeatedly accesses a single image file. Most of the time it works, but on a fast system the program sometimes tries to read the file before it has finished being written to disk, and an error is thrown:
The process cannot access the file because it is being used by another process.
I would like to avoid this problem. So far, most solutions I have found rely on catching exceptions when the file is locked. I would prefer a cleaner approach if possible. Is there a better way in C# to check whether a file is in use before trying to access it?
Short Answer
By the end of this page, you will understand why checking whether a file is "in use" is harder than it first appears, why a pre-check alone is unreliable, and how C# programs typically handle this safely using file access rules, retries, and exception handling where necessary.
Concept
In C#, a file can be temporarily unavailable because another process has opened it with a lock or with restrictive sharing rules. This often happens when one program is still writing the file while another tries to read it.
The important idea is this:
- There is no perfectly reliable "check first, then use" approach for files.
- A file's state can change immediately after you check it.
- This is a classic race condition.
For example, imagine you ask:
- "Is the file free right now?"
- The answer is yes.
- Another process locks the file one millisecond later.
- Your program tries to open it and still fails.
That is why exception handling is not just a fallback here—it is often the correct and necessary approach.
In real programming, the best solution usually combines:
- trying to open the file directly
- using the correct
FileSharemode - retrying for a short period if the file is still being written
- handling exceptions that can still occur
This matters because file access is shared with the operating system and other processes. Your code does not fully control when another program opens, writes, closes, or replaces a file.
Mental Model
Think of a file like a meeting room.
- If someone is inside and has locked the door, you cannot enter.
- You can peek at the sign and see whether the room looks free.
- But between checking the sign and opening the door, someone else might walk in and lock it.
So the only reliable test is actually trying the door.
That is how files work too:
- a pre-check can be out of date instantly
- the real test is attempting to open the file
- if it fails, your program must decide whether to wait, retry, or give up
Syntax and Examples
In C#, the common pattern is to try opening the file with a FileStream.
using System.IO;
bool IsFileAvailable(string path)
{
try
{
using (FileStream stream = new FileStream(
path,
FileMode.Open,
FileAccess.Read,
FileShare.None))
{
return true;
}
}
catch (IOException)
{
return false;
}
}
What this does
FileMode.Opentries to open an existing file.FileAccess.Readrequests read access.FileShare.Nonesays no other process may already be using it in a compatible way.- If the open succeeds, the file is available at that moment.
- If an
IOExceptionoccurs, the file is likely locked, still being written, or otherwise unavailable.
However, this function is only useful as a momentary test. It does not guarantee the file will still be free when you use it later.
Better pattern: retry when opening the file
System;
System.IO;
System.Threading;
{
( i = ; i < maxRetries; i++)
{
{
FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
}
(IOException)
{
(i == maxRetries - )
;
Thread.Sleep(delayMs);
}
}
IOException();
}
Step by Step Execution
Consider this example:
using System;
using System.IO;
using System.Threading;
string path = "image.png";
for (int attempt = 1; attempt <= 3; attempt++)
{
try
{
using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
Console.WriteLine($"Opened on attempt {attempt}");
break;
}
}
catch (IOException)
{
Console.WriteLine($"Attempt {attempt} failed");
Thread.Sleep(100);
}
}
Step by step
pathis set toimage.png.- The loop starts with
attempt = 1. - The program tries to open the file.
- If another process is still writing the file,
new FileStream(...)throwsIOException. - The
catchblock runs. - The program prints
Attempt 1 failed.
Real World Use Cases
This concept appears often in real applications:
- Image processing tools: waiting for a camera, screenshot tool, or editor to finish saving an image.
- Log readers: reading log files that another process is actively writing.
- Import jobs: watching a folder for CSV, JSON, or XML files that may arrive in stages.
- Desktop automation: opening exported reports only after Excel or another tool has finished writing them.
- File upload pipelines: processing files only after they are fully saved by another system.
In all of these cases, a file may exist but still not be ready to read safely.
Real Codebase Usage
In production code, developers usually do not build a separate "is file in use" check and then act on it. Instead, they use patterns like these:
Guarded open with retry
- Try to open the file immediately.
- If it fails with an
IOException, wait briefly and retry. - Stop after a maximum number of attempts.
This is simple and reliable.
Early return on missing file
Before worrying about locks, it is common to check whether the file exists:
if (!File.Exists(path))
return;
This handles one problem early, while still understanding that existence does not guarantee availability.
Validation before processing
After opening the file, code may validate:
- file size is greater than 0
- image can actually be decoded
- expected extension or format matches
Error handling and logging
Real systems often log retry attempts:
catch (IOException ex)
{
logger.LogWarning(ex, "File not ready yet: {Path}", path);
}
Reading with compatible sharing
Sometimes the writer allows readers while writing. In those cases, developers may use a less restrictive share mode such as FileShare.ReadWrite. That only works if partial reads are acceptable and the producing process supports it.
Common Mistakes
1. Checking first, then opening later
Broken idea:
if (IsFileAvailable(path))
{
// Assume it is safe
var text = File.ReadAllText(path);
}
Why it is a problem:
- The file can become locked after the check.
- This creates a race condition.
How to avoid it:
- Perform the actual open/read inside the
tryblock. - Retry if needed.
2. Treating exceptions as "bad design"
Beginners sometimes think exceptions should never be used for file access. But file I/O is an external system operation.
- disks fail
- files disappear
- permissions change
- other processes lock files
For these cases, exceptions are normal and expected.
3. Forgetting to close streams
Broken code:
FileStream stream = new FileStream(path, FileMode.Open);
// no Dispose or Close
Why it is a problem:
- Your own program may keep the file locked.
Fix:
using FileStream stream = new FileStream(path, FileMode.Open);
Comparisons
| Approach | How it works | Reliable? | Best use case |
|---|---|---|---|
File.Exists() | Checks whether the file path currently exists | No | Quick existence check only |
| Custom "is in use" check | Try to open the file separately, return true/false | Not fully | Temporary status checks |
Open directly with try/catch | Perform the real operation and handle failure | Yes, practical | Most real applications |
| Open with retry | Try, wait, and try again | Yes, very practical | Files that become available shortly |
FileSystemWatcher only | React to file system events | No |
Cheat Sheet
// Momentary availability check
bool IsFileAvailable(string path)
{
try
{
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.None);
return true;
}
catch (IOException)
{
return false;
}
}
// Practical pattern: open with retry
FileStream OpenFileWithRetry(string path, int maxRetries = 5, int delayMs = 200)
{
for (int i = 0; i < maxRetries; i++)
{
try
{
return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
}
catch (IOException)
{
if (i == maxRetries - 1) throw;
Thread.Sleep(delayMs);
}
}
throw new IOException();
}
Rules to remember
FAQ
Why can't I just check whether the file is in use first?
Because the file can change state immediately after the check. Another process may lock it before your next line of code runs.
Is using try/catch for file access really normal in C#?
Yes. File access depends on the operating system, disk, permissions, and other processes. Exceptions are the normal way to handle these failures.
What exception is usually thrown when a file is locked?
Usually IOException, though the exact reason may vary. Permission problems may throw UnauthorizedAccessException instead.
Should I use FileShare.None or FileShare.Read?
Use FileShare.None when you want strict exclusive access. Use FileShare.Read when shared reading is acceptable.
Can FileSystemWatcher tell me when the file is ready?
Not reliably. It tells you that something changed, not that writing is complete.
What is the best approach when another process is still saving the file?
Try opening the file, catch IOException, wait briefly, and retry a limited number of times.
Can I read a file while another process is writing it?
Sometimes, if the writer allows sharing and your code uses a compatible mode. But the content may be incomplete.
Mini Project
Description
Build a small C# utility that waits for an image file to become readable, then reports its size. This demonstrates the practical pattern used in real applications: do not trust a separate availability check; instead, retry the actual file open until the file is ready or a timeout is reached.
Goal
Create a program that safely opens a file that may still be locked by another process and prints basic information once it becomes available.
Requirements
- Ask the user for a file path.
- Check that the file exists before retrying.
- Retry opening the file a limited number of times.
- Wait briefly between attempts.
- Print the file size when the file opens successfully.
Keep learning
Related questions
AddTransient vs AddScoped vs AddSingleton in ASP.NET Core Dependency Injection
Learn the differences between AddTransient, AddScoped, and AddSingleton in ASP.NET Core DI with examples and practical usage.
Best Way to Repeat a Character in C#: Building Repeated Strings Efficiently
Learn the best way to repeat a character in C#, compare StringBuilder, string concatenation, and simpler built-in options.
C# Array Initialization Syntaxes Explained
Learn all common C# array initialization syntaxes with examples, rules, comparisons, and mistakes beginners often make.