Qualsiasi sviluppatore C# che non sia alle prime armi ha utilizzato almeno una volta il metodo File.ReadAllBytes per leggere il contenuto di un file presente sul FileSystem in locale e acquisirne il contenuto in memoria. Il metodo è fondamentalmente un wrapper che apre un'istanza FileStream per leggere il contenuto del file, quindi legge il contenuto dello stream e restituisce il suo array di byte per utilizzi di vario tipo.
Come ben spiegato nella documentazione ufficiale, il FileStream sottostante viene aperto utilizzando il flag FileShare.Read in combinazione con FileAccess.Read, il che significa che le chiamate simultanee al metodo File.ReadAllBytes sullo stesso file sono consentite: in altre parole, non vedremo mai una eccezione di tipo System.IO. IOException a causa del fatto che il file è utilizzato da un altro processo... a meno che il processo in questione non lo abbia già aperto in scrittura! Qualora questo dovesse verificarsi, la presenza del flag FileShare.Read determinerà il fallimento dell'operazione con il seguente messaggio di errore:
System.IO.IOException: the process can't access the file because it is being used by another process
System.IO.IOException: Il processo non può accedere al file perché è in uso da un altro processo.
Quando l'eccezione di cui sopra si verifica, abbiamo essenzialmente due strade da percorrere:
- Attendere e riprovare fino al termine del writing lock, ovvero fino a quando il processo che sta scrivendo non ha terminato. Questo tipo di approccio è spiegato molto bene in questo thread su StackOverflow.
- Rinunciare al metodo File.ReadAllBytes (e metodi similari) e aprire manualmente un FileStream utilizzando il flag FileShare.ReadWrite al posto del flagFileShare.Read, il quale è utilizzato internamente dai metodi File.ReadAllBytes e similari (come spiegato in questo altro thread sempre su StackOverflow).
Il primo workaroud è certamente percorribile, ma rischia di creare problematiche di thread-locking nel malaugurato caso in cui l'attività di scrittura del processo che si attende dovesse prolungarsi: per questo motivo è quasi sempre preferibile utilizzare il secondo metodo, a patto ovviamente di non avere esigenze particolari.
Ecco un rapido esempio di implementazione che mostra un metodo molto simile al classico File.ReadAllBytes ma con la possibilità aggiuntiva di specificare uno o più flag FileShare a piacere:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/// <summary> /// File.ReadAllBytes alternative to avoid read and/or write locking /// </summary> private byte[] ReadAllBytes2(string filePath, FileAccess = FileAccess.Read, FileShare shareMode = FileShare.ReadWrite) { using (var fs = new FileStream(filePath, FileMode.Open, fileAccess, shareMode)) { using (var ms = new MemoryStream()) { fs.CopyTo(ms); return ms.ToArray(); } } } |
Per il momento è tutto: spero che questa breve analisi potrà rivelarsi d'aiuto a quanti si imbatteranno in questa problematica!