Mam trochę kodu i kiedy się wykonuje, rzuca IOException
, mówiąc to
Proces nie może uzyskać dostępu do pliku „nazwa_pliku”, ponieważ jest używany przez inny proces
Co to oznacza i co mogę z tym zrobić?
Mam trochę kodu i kiedy się wykonuje, rzuca IOException
, mówiąc to
Proces nie może uzyskać dostępu do pliku „nazwa_pliku”, ponieważ jest używany przez inny proces
Co to oznacza i co mogę z tym zrobić?
Odpowiedzi:
Komunikat o błędzie jest dość jasny: próbujesz uzyskać dostęp do pliku i nie jest on dostępny, ponieważ inny proces (lub nawet ten sam proces) coś z nim robi (i nie pozwala na żadne udostępnianie).
Może to być dość łatwe do rozwiązania (lub dość trudne do zrozumienia), w zależności od konkretnego scenariusza. Zobaczmy trochę.
Twój proces jest jedynym, który ma dostęp do tego pliku.
Masz pewność, że drugi proces to Twój własny proces. Jeśli wiesz, że otwierasz ten plik w innej części programu, to przede wszystkim musisz sprawdzić, czy po każdym użyciu poprawnie zamykasz uchwyt pliku. Oto przykład kodu z tym błędem:
var stream = new FileStream(path, FileAccess.Read);
var reader = new StreamReader(stream);
// Read data from this file, when I'm done I don't need it any more
File.Delete(path); // IOException: file is in use
Na szczęście FileStream
implementuje IDisposable
, więc łatwo jest opakować cały kod w using
instrukcję:
using (var stream = File.Open("myfile.txt", FileMode.Open)) {
// Use stream
}
// Here stream is not accessible and it has been closed (also if
// an exception is thrown and stack unrolled
Ten wzorzec zapewni również, że plik nie zostanie otwarty w przypadku wyjątków (może to być powód, dla którego plik jest używany: coś poszło nie tak i nikt go nie zamknął; zobacz przykład w tym poście ).
Jeśli wszystko wydaje się w porządku (jesteś pewien, że zawsze zamykasz każdy otwierany plik, nawet w przypadku wyjątków) i masz wiele działających wątków, masz dwie opcje: przerobić kod w celu serializacji dostępu do plików (nie zawsze jest to wykonalne i nie zawsze potrzebne) lub zastosuj wzorzec ponawiania . To dość powszechny wzorzec dla operacji I / O: próbujesz coś zrobić, aw przypadku błędu czekasz i próbujesz ponownie (czy zadałeś sobie pytanie, dlaczego na przykład powłoka systemu Windows potrzebuje trochę czasu, aby poinformować Cię, że plik jest używany i nie można go usunąć?). W C # jest to dość łatwe do zaimplementowania (zobacz także lepsze przykłady dotyczące operacji we / wy dysków , sieci i dostępu do bazy danych ).
private const int NumberOfRetries = 3;
private const int DelayOnRetry = 1000;
for (int i=1; i <= NumberOfRetries; ++i) {
try {
// Do stuff with file
break; // When done we can break loop
}
catch (IOException e) when (i <= NumberOfRetries) {
// You may check error code to filter some exceptions, not every error
// can be recovered.
Thread.Sleep(DelayOnRetry);
}
}
Zwróć uwagę na typowy błąd, który bardzo często widzimy w StackOverflow:
var stream = File.Open(path, FileOpen.Read);
var content = File.ReadAllText(path);
W tym przypadku ReadAllText()
nie powiedzie się, ponieważ plik jest używany ( File.Open()
w linii przed). Wcześniejsze otwarcie pliku jest nie tylko niepotrzebne, ale także złe. To samo odnosi się do wszystkich File
funkcji, które nie zwracają się uchwyt do pliku, nad którym pracujesz z: File.ReadAllText()
, File.WriteAllText()
, File.ReadAllLines()
, File.WriteAllLines()
i innych (takich File.AppendAllXyz()
funkcji) będzie wszystko otwarte i zamknij plik przez siebie.
Twój proces nie jest jedynym, który ma dostęp do tego pliku.
Jeśli Twój proces nie jest jedynym, który ma dostęp do tego pliku, interakcja może być trudniejsza. Wzór ponawiania pomoże (jeśli plik nie powinien być otwarty przez kogoś innego, ale to jest, to trzeba podobnego narzędzia Process Explorer, aby sprawdzić , kto robi to, co ).
W stosownych przypadkach zawsze używaj instrukcji using do otwierania plików. Jak wspomniano w poprzednim akapicie, pomoże ci to aktywnie uniknąć wielu typowych błędów (zobacz ten post, aby zobaczyć przykład, jak go nie używać ).
Jeśli to możliwe, spróbuj zdecydować, kto jest właścicielem dostępu do określonego pliku i scentralizuj dostęp za pomocą kilku dobrze znanych metod. Jeśli na przykład masz plik danych, w którym program czyta i zapisuje, to cały kod I / O powinien być umieszczony w jednej klasie. Ułatwi to debugowanie (ponieważ zawsze możesz umieścić tam punkt przerwania i zobaczyć, kto robi co), a także będzie to punkt synchronizacji (jeśli jest to wymagane) dla wielokrotnego dostępu.
Nie zapominaj, że operacje we / wy mogą zawsze kończyć się niepowodzeniem, typowy przykład jest następujący:
if (File.Exists(path))
File.Delete(path);
Jeśli ktoś usunie plik po, File.Exists()
ale wcześniej File.Delete()
, wyrzuci on IOException
miejsce, w którym możesz niesłusznie czuć się bezpiecznie.
Jeśli to możliwe, zastosuj wzorzec ponawiania , a jeśli używasz FileSystemWatcher
, rozważ odroczenie akcji (ponieważ otrzymasz powiadomienie, ale aplikacja może nadal pracować wyłącznie z tym plikiem).
Scenariusze zaawansowane
Nie zawsze jest to takie proste, więc może być konieczne udostępnienie dostępu komuś innemu. Jeśli na przykład czytasz od początku i piszesz do końca, masz co najmniej dwie możliwości.
1) udostępniać to samo FileStream
z odpowiednimi funkcjami synchronizacji (ponieważ nie jest bezpieczny wątkowo ). Zobacz przykład tego i tego postu.
2) użyj FileShare
wyliczania, aby poinstruować system operacyjny, aby zezwalał innym procesom (lub innym częściom twojego własnego procesu) na jednoczesny dostęp do tego samego pliku.
using (var stream = File.Open(path, FileMode.Open, FileAccess.Write, FileShare.Read))
{
}
W tym przykładzie pokazałem, jak otworzyć plik do zapisu i udostępnić do czytania; należy pamiętać, że podczas odczytywania i zapisywania nakładania się, skutkuje to niezdefiniowanymi lub nieprawidłowymi danymi. Jest to sytuacja, z którą trzeba sobie poradzić podczas czytania. Należy również pamiętać, że nie zapewnia to dostępu do stream
wątku bezpiecznego, więc ten obiekt nie może być udostępniany wielu wątkom, chyba że dostęp jest w jakiś sposób zsynchronizowany (zobacz poprzednie linki). Dostępne są inne opcje udostępniania, które otwierają bardziej złożone scenariusze. Więcej informacji można znaleźć w witrynie MSDN .
Ogólnie N procesów może czytać z tego samego pliku razem, ale tylko jeden powinien pisać, w kontrolowanym scenariuszu możesz nawet włączyć współbieżne zapisy, ale nie można tego uogólnić w kilku akapitach tekstu w tej odpowiedzi.
Czy można odblokować plik używany przez inny proces? Nie zawsze jest to bezpieczne i nie jest takie łatwe, ale tak, jest to możliwe .
File.Create(path)
, powinieneś dodać .Close()
na końcu tego, zanim napiszesz do niego. Oprócz using
instrukcji dotyczących zapisywania plików, a następnie usuwania po nich, są takie pułapki . W swoim pytaniu powinieneś zamieścić kod dotyczący sposobu tworzenia i usuwania pliku. Ale prawdopodobnie wpisuje się w coś wspomnianego powyżej.
Directory.SetCreationTimeUTC()
ale kończy się niepowodzeniem, gdy Eksplorator plików jest otwarty, twierdząc, że katalog jest używany przez inny proces. Jak mam sobie z tym poradzić?
Korzystanie z FileShare rozwiązało mój problem z otwieraniem pliku, nawet jeśli jest otwierany przez inny proces.
using (var stream = File.Open(path, FileMode.Open, FileAccess.Write, FileShare.ReadWrite))
{
}
Wystąpił problem podczas przesyłania obrazu, nie można go usunąć i znalazłem rozwiązanie. gl hf
//C# .NET
var image = Image.FromFile(filePath);
image.Dispose(); // this removes all resources
//later...
File.Delete(filePath); //now works
Otrzymałem ten błąd, ponieważ robiłem File.Move do ścieżki pliku bez nazwy pliku, muszę określić pełną ścieżkę w miejscu docelowym.
Błąd wskazuje, że inny proces próbuje uzyskać dostęp do pliku. Może ty lub ktoś inny otworzył ją, gdy próbujesz do niej napisać. „Czytaj” lub „Kopiuj” zwykle tego nie powoduje, ale pisanie do niego lub wywoływanie usuwania na nim tak.
Jest kilka podstawowych rzeczy, których należy unikać, o czym wspomniały inne odpowiedzi:
W FileStream
operacjach umieść go w using
bloku z FileShare.ReadWrite
trybem dostępu.
Na przykład:
using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Write, FileShare.ReadWrite))
{
}
Pamiętaj, że FileAccess.ReadWrite
nie jest to możliwe, jeśli używasz FileMode.Append
.
Natknąłem się na ten problem, gdy używałem strumienia wejściowego do wykonania, File.SaveAs
gdy plik był używany. W moim przypadku nie musiałem w ogóle zapisywać go z powrotem w systemie plików, więc skończyło się na tym, że po prostu to usunąłem, ale prawdopodobnie mogłem spróbować utworzyć FileStream w using
instrukcji z FileAccess.ReadWrite
, podobnie jak kod powyżej.
Zapisanie danych jako inny plik i powrót do usunięcia starego, gdy okaże się, że nie jest już używany, a następnie zmiana nazwy pliku, który został pomyślnie zapisany, na nazwę oryginalnego jest opcją. Sposób testowania używanego pliku odbywa się za pomocą
List<Process> lstProcs = ProcessHandler.WhoIsLocking(file);
linii w moim kodzie poniżej i można to zrobić w usłudze Windows, w pętli, jeśli masz określony plik, który chcesz regularnie oglądać i usuwać, gdy chcesz go zastąpić. Jeśli nie zawsze masz ten sam plik, można zaktualizować plik tekstowy lub tabelę bazy danych, aby usługa zawsze sprawdzała nazwy plików, a następnie przeprowadza sprawdzenie procesów, a następnie wykonuje zabijanie i usuwanie procesów na nim, jak to opisuję w następnej opcji. Zauważ, że będziesz potrzebować nazwy użytkownika i hasła do konta, które ma uprawnienia administratora na danym komputerze, oczywiście, aby usunąć i zakończyć procesy.
Jeśli nie wiesz, czy plik będzie używany, gdy próbujesz go zapisać, możesz zamknąć wszystkie procesy, które mogą go używać, takie jak Word, jeśli jest to dokument programu Word, przed zapisaniem.
Jeśli jest lokalny, możesz to zrobić:
ProcessHandler.localProcessKill("winword.exe");
Jeśli jest zdalny, możesz to zrobić:
ProcessHandler.remoteProcessKill(computerName, txtUserName, txtPassword, "winword.exe");
gdzie txtUserName
jest w postaci DOMAIN\user
.
Powiedzmy, że nie znasz nazwy procesu, który blokuje plik. Następnie możesz to zrobić:
List<Process> lstProcs = new List<Process>();
lstProcs = ProcessHandler.WhoIsLocking(file);
foreach (Process p in lstProcs)
{
if (p.MachineName == ".")
ProcessHandler.localProcessKill(p.ProcessName);
else
ProcessHandler.remoteProcessKill(p.MachineName, txtUserName, txtPassword, p.ProcessName);
}
Zauważ, że file
musi to być ścieżka UNC: \\computer\share\yourdoc.docx
aby program Process
mógł dowiedzieć się, na jakim komputerze się znajduje i p.MachineName
jest ważny.
Poniżej znajduje się klasa, której używają te funkcje, do której należy dodać odwołanie System.Management
. Kod został pierwotnie napisany przez Erica J .:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Management;
namespace MyProject
{
public static class ProcessHandler
{
[StructLayout(LayoutKind.Sequential)]
struct RM_UNIQUE_PROCESS
{
public int dwProcessId;
public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
}
const int RmRebootReasonNone = 0;
const int CCH_RM_MAX_APP_NAME = 255;
const int CCH_RM_MAX_SVC_NAME = 63;
enum RM_APP_TYPE
{
RmUnknownApp = 0,
RmMainWindow = 1,
RmOtherWindow = 2,
RmService = 3,
RmExplorer = 4,
RmConsole = 5,
RmCritical = 1000
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct RM_PROCESS_INFO
{
public RM_UNIQUE_PROCESS Process;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)]
public string strAppName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)]
public string strServiceShortName;
public RM_APP_TYPE ApplicationType;
public uint AppStatus;
public uint TSSessionId;
[MarshalAs(UnmanagedType.Bool)]
public bool bRestartable;
}
[DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
static extern int RmRegisterResources(uint pSessionHandle,
UInt32 nFiles,
string[] rgsFilenames,
UInt32 nApplications,
[In] RM_UNIQUE_PROCESS[] rgApplications,
UInt32 nServices,
string[] rgsServiceNames);
[DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)]
static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey);
[DllImport("rstrtmgr.dll")]
static extern int RmEndSession(uint pSessionHandle);
[DllImport("rstrtmgr.dll")]
static extern int RmGetList(uint dwSessionHandle,
out uint pnProcInfoNeeded,
ref uint pnProcInfo,
[In, Out] RM_PROCESS_INFO[] rgAffectedApps,
ref uint lpdwRebootReasons);
/// <summary>
/// Find out what process(es) have a lock on the specified file.
/// </summary>
/// <param name="path">Path of the file.</param>
/// <returns>Processes locking the file</returns>
/// <remarks>See also:
/// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373661(v=vs.85).aspx
/// http://wyupdate.googlecode.com/svn-history/r401/trunk/frmFilesInUse.cs (no copyright in code at time of viewing)
///
/// </remarks>
static public List<Process> WhoIsLocking(string path)
{
uint handle;
string key = Guid.NewGuid().ToString();
List<Process> processes = new List<Process>();
int res = RmStartSession(out handle, 0, key);
if (res != 0) throw new Exception("Could not begin restart session. Unable to determine file locker.");
try
{
const int ERROR_MORE_DATA = 234;
uint pnProcInfoNeeded = 0,
pnProcInfo = 0,
lpdwRebootReasons = RmRebootReasonNone;
string[] resources = new string[] { path }; // Just checking on one resource.
res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null);
if (res != 0) throw new Exception("Could not register resource.");
//Note: there's a race condition here -- the first call to RmGetList() returns
// the total number of process. However, when we call RmGetList() again to get
// the actual processes this number may have increased.
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons);
if (res == ERROR_MORE_DATA)
{
// Create an array to store the process results
RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
pnProcInfo = pnProcInfoNeeded;
// Get the list
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
if (res == 0)
{
processes = new List<Process>((int)pnProcInfo);
// Enumerate all of the results and add them to the
// list to be returned
for (int i = 0; i < pnProcInfo; i++)
{
try
{
processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId));
}
// catch the error -- in case the process is no longer running
catch (ArgumentException) { }
}
}
else throw new Exception("Could not list processes locking resource.");
}
else if (res != 0) throw new Exception("Could not list processes locking resource. Failed to get size of result.");
}
finally
{
RmEndSession(handle);
}
return processes;
}
public static void remoteProcessKill(string computerName, string userName, string pword, string processName)
{
var connectoptions = new ConnectionOptions();
connectoptions.Username = userName;
connectoptions.Password = pword;
ManagementScope scope = new ManagementScope(@"\\" + computerName + @"\root\cimv2", connectoptions);
// WMI query
var query = new SelectQuery("select * from Win32_process where name = '" + processName + "'");
using (var searcher = new ManagementObjectSearcher(scope, query))
{
foreach (ManagementObject process in searcher.Get())
{
process.InvokeMethod("Terminate", null);
process.Dispose();
}
}
}
public static void localProcessKill(string processName)
{
foreach (Process p in Process.GetProcessesByName(processName))
{
p.Kill();
}
}
[DllImport("kernel32.dll")]
public static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, int dwFlags);
public const int MOVEFILE_DELAY_UNTIL_REBOOT = 0x4;
}
}
Jak wskazywały inne odpowiedzi w tym wątku, aby rozwiązać ten błąd, musisz dokładnie sprawdzić kod, aby zrozumieć, gdzie plik jest blokowany.
W moim przypadku wysyłałem plik jako załącznik do wiadomości e-mail przed wykonaniem operacji przenoszenia.
Plik został więc zablokowany na kilka sekund, aż klient SMTP zakończył wysyłanie wiadomości e-mail.
Rozwiązaniem, które zastosowałem, było najpierw przeniesienie pliku, a następnie wysłanie e-maila. To rozwiązało problem.
Innym możliwym rozwiązaniem, jak wskazał wcześniej Hudson, byłoby pozbycie się przedmiotu po użyciu.
public static SendEmail()
{
MailMessage mMailMessage = new MailMessage();
//setup other email stuff
if (File.Exists(attachmentPath))
{
Attachment attachment = new Attachment(attachmentPath);
mMailMessage.Attachments.Add(attachment);
attachment.Dispose(); //disposing the Attachment object
}
}
File.Move()
, nie działa i wyświetla ten sam błąd. Jeśli po prostu dodam plik do wiadomości e-mail, nie sądzę, że wystąpi błąd podczas używania podczas Attachments.Add()
operacji, ponieważ jest to tylko operacja kopiowania. Jeśli z jakiegoś powodu tak się stało, możesz skopiować go do katalogu Temp, dołączyć kopię, a następnie usunąć skopiowany plik. Ale nie sądzę, że jeśli OP chce zmodyfikować plik i użyć tego, to tego rodzaju rozwiązanie (którego nie pokazałeś kodu, tylko część załączająca) zadziałałoby. .Dispose()
jest zawsze dobrym pomysłem, ale nie ma tu znaczenia, chyba że plik zostanie otwarty we wcześniejszej operacji.
Miałem następujący scenariusz, który powodował ten sam błąd:
Większość plików miała mały rozmiar, jednak kilka było dużych, więc próba ich usunięcia spowodowała brak dostępu do pliku błąd .
Nie było to łatwe do znalezienia, jednak rozwiązanie było tak proste, jak oczekiwanie „na zakończenie realizacji zadania”:
using (var wc = new WebClient())
{
var tskResult = wc.UploadFileTaskAsync(_address, _fileName);
tskResult.Wait();
}
Mój poniższy kod rozwiązuje ten problem, ale sugeruję Przede wszystkim musisz zrozumieć, co powoduje ten problem i wypróbować rozwiązanie, które możesz znaleźć, zmieniając kod
Mogę podać inny sposób rozwiązania tego problemu, ale lepszym rozwiązaniem jest sprawdzenie struktury kodowania i próba przeanalizowania, co tak się dzieje, jeśli nie znajdziesz żadnego rozwiązania, możesz przejść z tym kodem poniżej
try{
Start:
///Put your file access code here
}catch (Exception ex)
{
//by anyway you need to handle this error with below code
if (ex.Message.StartsWith("The process cannot access the file"))
{
//Wait for 5 seconds to free that file and then start execution again
Thread.Sleep(5000);
goto Start;
}
}
GC.*()
, prawdopodobnie masz inne problemy z kodem. 2) Wiadomość jest zlokalizowana i krucha , zamiast tego użyj HRESULT. 3) Możesz chcieć spać Task.Delay()
(a dziesięć sekund to w wielu przypadkach zbyt wiele). 4) Nie masz warunku wyjścia: ten kod może zawiesić się na zawsze. 5) Na pewno nie potrzebujesz goto
tutaj. 6) Złapanie Exception
jest zwykle złym pomysłem, w tym przypadku także dlatego, że ... 6) Jeśli zdarzy się coś innego, to połykasz błąd.
GC.Collect()
, dotyczył niektórych obiektów COM.