Tôi có một số mã và khi nó thực thi, nó ném một IOException
, nói rằng
Quá trình không thể truy cập vào tệp 'tên tệp' vì nó đang được sử dụng bởi một quy trình khác
Điều này có nghĩa là gì, và tôi có thể làm gì về nó?
Tôi có một số mã và khi nó thực thi, nó ném một IOException
, nói rằng
Quá trình không thể truy cập vào tệp 'tên tệp' vì nó đang được sử dụng bởi một quy trình khác
Điều này có nghĩa là gì, và tôi có thể làm gì về nó?
Câu trả lời:
Thông báo lỗi khá rõ ràng: bạn đang cố truy cập vào một tệp và không thể truy cập được vì một quy trình khác (hoặc thậm chí cùng quy trình) đang làm gì đó với nó (và nó không cho phép chia sẻ).
Nó có thể khá dễ để giải quyết (hoặc khá khó hiểu), tùy thuộc vào kịch bản cụ thể của bạn. Chúng ta hãy xem một số.
Quá trình của bạn là người duy nhất truy cập vào tệp đó
Bạn chắc chắn rằng quy trình khác là quy trình của riêng bạn. Nếu bạn biết bạn mở tệp đó trong một phần khác của chương trình, thì trước hết bạn phải kiểm tra xem bạn có đóng đúng cách xử lý tệp sau mỗi lần sử dụng không. Dưới đây là một ví dụ về mã với lỗi này:
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
May mắn là FileStream
thực hiện IDisposable
, vì vậy thật dễ dàng để bọc tất cả mã của bạn trong một using
câu lệnh:
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
Mô hình này cũng sẽ đảm bảo rằng các tập tin sẽ không bị bỏ ngỏ trong trường hợp ngoại lệ (đó có thể là lý do các tập tin được sử dụng: một cái gì đó đã đi sai, và không ai đóng cửa, xem bài này cho một ví dụ).
Nếu mọi thứ có vẻ ổn (bạn chắc chắn rằng bạn luôn đóng mọi tệp bạn mở, ngay cả trong trường hợp ngoại lệ) và bạn có nhiều luồng làm việc, thì bạn có hai tùy chọn: làm lại mã của mình để tuần tự truy cập tệp (không phải lúc nào cũng có thể thực hiện được và không phải lúc nào cũng muốn) hoặc áp dụng một mẫu thử lại . Đây là một mô hình khá phổ biến cho các thao tác I / O: bạn cố gắng làm điều gì đó và trong trường hợp có lỗi, bạn chờ và thử lại (ví dụ, bạn có tự hỏi tại sao, Windows Shell phải mất một thời gian để thông báo cho bạn rằng một tệp đang được sử dụng và không thể xóa?). Trong C #, nó khá dễ thực hiện (xem thêm các ví dụ tốt hơn về I / O đĩa , truy cập mạng và cơ sở dữ liệu ).
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);
}
}
Xin lưu ý một lỗi phổ biến chúng tôi thấy rất thường xuyên trên StackOverflow:
var stream = File.Open(path, FileOpen.Read);
var content = File.ReadAllText(path);
Trong trường hợp ReadAllText()
này sẽ thất bại vì tập tin đang được sử dụng ( File.Open()
trong dòng trước). Để mở tập tin trước không chỉ không cần thiết mà còn sai. Điều tương tự cũng áp dụng cho tất cả các File
chức năng mà không trả về một tay cầm đến tập tin bạn đang làm việc với: File.ReadAllText()
, File.WriteAllText()
, File.ReadAllLines()
, File.WriteAllLines()
và những người khác (như File.AppendAllXyz()
chức năng) sẽ tất cả mở và đóng các tập tin bằng bản thân.
Quá trình của bạn không phải là người duy nhất truy cập tệp đó
Nếu quy trình của bạn không phải là người duy nhất truy cập tệp đó, thì tương tác có thể khó khăn hơn. Một mẫu thử lại sẽ giúp ích (nếu tệp không nên được mở bởi bất kỳ ai khác ngoài nó, thì bạn cần một tiện ích như Process Explorer để kiểm tra xem ai đang làm gì ).
Khi có thể, luôn luôn sử dụng các câu lệnh để mở tệp. Như đã nói ở đoạn trước, nó sẽ chủ động giúp bạn tránh được nhiều lỗi phổ biến (xem bài đăng này để biết ví dụ về cách không sử dụng nó ).
Nếu có thể, hãy cố gắng quyết định ai sở hữu quyền truy cập vào một tệp cụ thể và tập trung quyền truy cập thông qua một vài phương pháp nổi tiếng. Ví dụ, nếu bạn có một tệp dữ liệu nơi chương trình của bạn đọc và ghi, thì bạn nên đóng hộp tất cả mã I / O bên trong một lớp. Nó sẽ giúp gỡ lỗi dễ dàng hơn (vì bạn luôn có thể đặt điểm dừng ở đó và xem ai đang làm gì) và đó cũng sẽ là điểm đồng bộ hóa (nếu cần) cho nhiều truy cập.
Đừng quên các thao tác I / O luôn có thể thất bại, một ví dụ phổ biến là:
if (File.Exists(path))
File.Delete(path);
Nếu ai đó xóa tệp sau File.Exists()
nhưng trước File.Delete()
đó, thì nó sẽ ném IOException
vào một nơi mà bạn có thể cảm thấy an toàn.
Bất cứ khi nào có thể, hãy áp dụng mẫu thử lại và nếu bạn đang sử dụng FileSystemWatcher
, hãy xem xét hành động hoãn lại (vì bạn sẽ được thông báo, nhưng một ứng dụng vẫn có thể hoạt động riêng với tệp đó).
Kịch bản nâng cao
Không phải lúc nào cũng dễ dàng, vì vậy bạn có thể cần chia sẻ quyền truy cập với người khác. Ví dụ, nếu bạn đọc từ đầu và viết đến cuối, bạn có ít nhất hai tùy chọn.
1) chia sẻ tương tự FileStream
với các chức năng đồng bộ hóa thích hợp (vì nó không an toàn cho luồng ). Xem điều này và bài viết này cho một ví dụ.
2) sử dụng FileShare
phép liệt kê để hướng dẫn HĐH cho phép các quy trình khác (hoặc các phần khác trong quy trình của riêng bạn) truy cập cùng một tệp.
using (var stream = File.Open(path, FileMode.Open, FileAccess.Write, FileShare.Read))
{
}
Trong ví dụ này tôi đã chỉ ra cách mở một tệp để viết và chia sẻ để đọc; xin lưu ý rằng khi đọc và viết chồng chéo, nó dẫn đến dữ liệu không xác định hoặc không hợp lệ. Đó là một tình huống phải được xử lý khi đọc. Cũng lưu ý rằng điều này không làm cho quyền truy cập vào stream
luồng an toàn, vì vậy đối tượng này không thể được chia sẻ với nhiều luồng trừ khi quyền truy cập được đồng bộ hóa bằng cách nào đó (xem các liên kết trước đó). Các tùy chọn chia sẻ khác có sẵn và chúng mở ra các kịch bản phức tạp hơn. Vui lòng tham khảo MSDN để biết thêm chi tiết.
Nói chung, các quy trình N có thể đọc từ cùng một tệp nhưng chỉ một người nên viết, trong một kịch bản được kiểm soát, bạn thậm chí có thể kích hoạt các bài viết đồng thời nhưng điều này không thể được khái quát trong một vài đoạn văn bản trong câu trả lời này.
Có thể mở khóa một tập tin được sử dụng bởi một quá trình khác? Nó không phải lúc nào cũng an toàn và không dễ dàng nhưng có, nó có thể .
File.Create(path)
, bạn nên thêm .Close()
vào cuối của nó trước khi bạn viết nó. Có những cạm bẫy như thế, ngoài các using
câu lệnh để ghi các tệp, sau đó xóa chúng. Bạn nên đăng mã trong câu hỏi của bạn về cách bạn đang tạo và xóa tệp của mình. Nhưng có lẽ rơi vào phù hợp với một cái gì đó được đề cập ở trên.
Directory.SetCreationTimeUTC()
nhưng không thành công khi File Explorer đang mở, tuyên bố thư mục đang được truy cập bởi một quy trình khác. Tôi nên xử lý tình huống này như thế nào?
Sử dụng FileShare đã khắc phục sự cố mở tệp của tôi ngay cả khi nó được mở bởi một quy trình khác.
using (var stream = File.Open(path, FileMode.Open, FileAccess.Write, FileShare.ReadWrite))
{
}
Có một vấn đề trong khi tải lên một hình ảnh và không thể xóa nó và tìm thấy một giải pháp. gl hf
//C# .NET
var image = Image.FromFile(filePath);
image.Dispose(); // this removes all resources
//later...
File.Delete(filePath); //now works
Tôi đã gặp lỗi này vì tôi đang thực hiện File.Move đến một đường dẫn tệp không có tên tệp, cần chỉ định đường dẫn đầy đủ ở đích.
Lỗi cho thấy một quá trình khác đang cố gắng truy cập tệp. Có thể bạn hoặc người khác mở nó trong khi bạn đang cố viết cho nó. "Đọc" hoặc "Sao chép" thường không gây ra điều này, nhưng viết thư cho nó hoặc gọi xóa trên đó.
Có một số điều cơ bản để tránh điều này, như các câu trả lời khác đã đề cập:
Trong các FileStream
hoạt động, đặt nó trong một using
khối với một FileShare.ReadWrite
chế độ truy cập.
Ví dụ:
using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Write, FileShare.ReadWrite))
{
}
Lưu ý rằng FileAccess.ReadWrite
không thể nếu bạn sử dụng FileMode.Append
.
Tôi đã chạy qua vấn đề này khi tôi đang sử dụng một luồng đầu vào để thực hiện File.SaveAs
khi tập tin được sử dụng. Trong trường hợp của tôi, tôi đã tìm thấy, tôi thực sự không cần phải lưu nó trở lại hệ thống tệp, vì vậy cuối cùng tôi đã gỡ bỏ nó, nhưng tôi có thể đã thử tạo FileStream trong một using
câu lệnh FileAccess.ReadWrite
, giống như mã ở trên.
Lưu dữ liệu của bạn dưới dạng một tệp khác và quay lại xóa dữ liệu cũ khi phát hiện thấy nó không còn được sử dụng, sau đó đổi tên dữ liệu đã lưu thành công thành tên của tệp gốc là một tùy chọn. Cách bạn kiểm tra tệp đang sử dụng được thực hiện thông qua
List<Process> lstProcs = ProcessHandler.WhoIsLocking(file);
trong mã của tôi dưới đây và có thể được thực hiện trong một dịch vụ Windows, trên một vòng lặp, nếu bạn có một tệp cụ thể bạn muốn xem và xóa thường xuyên khi bạn muốn thay thế nó. Nếu bạn không luôn có cùng một tệp, một tệp văn bản hoặc bảng cơ sở dữ liệu có thể được cập nhật rằng dịch vụ luôn kiểm tra tên tệp và sau đó thực hiện kiểm tra các quy trình và sau đó thực hiện quy trình giết và xóa nó, như tôi mô tả trong tùy chọn tiếp theo. Lưu ý rằng bạn sẽ cần một tên người dùng và mật khẩu tài khoản có đặc quyền Quản trị viên trên máy tính đã cho, tất nhiên, để thực hiện xóa và kết thúc các quy trình.
Khi bạn không biết liệu một tệp sẽ được sử dụng khi bạn đang cố lưu nó, bạn có thể đóng tất cả các quy trình có thể đang sử dụng, như Word, nếu đó là tài liệu Word, trước khi lưu.
Nếu nó là địa phương, bạn có thể làm điều này:
ProcessHandler.localProcessKill("winword.exe");
Nếu nó ở xa, bạn có thể làm điều này:
ProcessHandler.remoteProcessKill(computerName, txtUserName, txtPassword, "winword.exe");
ở đâu txtUserName
dưới dạng DOMAIN\user
.
Giả sử bạn không biết tên quy trình đang khóa tệp. Sau đó, bạn có thể làm điều này:
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);
}
Lưu ý rằng file
phải là đường dẫn UNC: \\computer\share\yourdoc.docx
để cho Process
để tìm ra những gì máy tính nó vào và p.MachineName
có giá trị.
Dưới đây là lớp mà các hàm này sử dụng, yêu cầu thêm một tham chiếu đến System.Management
. Mã ban đầu được viết bởi Eric 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;
}
}
Như các câu trả lời khác trong chủ đề này đã chỉ ra, để khắc phục lỗi này, bạn cần kiểm tra cẩn thận mã, để hiểu nơi tệp bị khóa.
Trong trường hợp của tôi, tôi đã gửi tệp dưới dạng tệp đính kèm email trước khi thực hiện thao tác di chuyển.
Vì vậy, tệp đã bị khóa trong vài giây cho đến khi máy khách SMTP hoàn tất việc gửi email.
Giải pháp tôi áp dụng là di chuyển tệp trước, sau đó gửi email. Điều này giải quyết các vấn đề đối với tôi.
Một giải pháp khả thi khác, như Hudson đã chỉ ra trước đó, sẽ là vứt bỏ đồ vật sau khi sử dụng.
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()
sẽ không hoạt động và gây ra lỗi tương tự. Nếu chỉ thêm một tệp vào e-mail, tôi không nghĩ nó bị lỗi khi sử dụng trong quá trình Attachments.Add()
hoạt động vì đây chỉ là một thao tác sao chép. Nếu vì lý do nào đó, bạn có thể sao chép nó vào thư mục Temp, đính kèm bản sao và xóa tệp đã sao chép sau đó. Nhưng tôi không nghĩ, nếu OP muốn sửa đổi một tệp và sử dụng tệp đó, thì loại giải pháp này (mà bạn không hiển thị mã, chỉ phần đính kèm) sẽ hoạt động. .Dispose()
luôn luôn là một ý tưởng tốt, nhưng không liên quan ở đây trừ khi tập tin được mở trong một op trước.
Tôi đã có kịch bản sau đây gây ra lỗi tương tự:
Hầu hết các tệp có kích thước nhỏ, tuy nhiên, một số tệp lớn và do đó cố gắng xóa những tệp đó dẫn đến lỗi không thể truy cập tệp .
Thật không dễ tìm, tuy nhiên, giải pháp đơn giản như Chờ đợi "để tác vụ hoàn thành thực thi":
using (var wc = new WebClient())
{
var tskResult = wc.UploadFileTaskAsync(_address, _fileName);
tskResult.Wait();
}
Mã dưới đây của tôi giải quyết vấn đề này, nhưng tôi đề nghị Trước hết bạn cần hiểu nguyên nhân gây ra sự cố này và thử giải pháp mà bạn có thể tìm thấy bằng cách thay đổi mã
Tôi có thể đưa ra một cách khác để giải quyết vấn đề này nhưng giải pháp tốt hơn là kiểm tra cấu trúc mã hóa của bạn và cố gắng phân tích điều gì xảy ra, nếu bạn không tìm thấy bất kỳ giải pháp nào thì bạn có thể đi với mã này bên dưới
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.*()
thì có lẽ bạn có vấn đề khác với mã của mình. 2) Tin nhắn được bản địa hóa và dễ vỡ , thay vào đó hãy sử dụng HRESULT. 3) Bạn có thể muốn ngủ với Task.Delay()
(và mười giây bằng cách nào đó quá mức trong nhiều trường hợp). 4) Bạn không có điều kiện thoát: mã này có thể treo forver. 5) Bạn chắc chắn không cần goto
ở đây. 6) Để bắt Exception
thường là một ý tưởng tồi, trong trường hợp này cũng bởi vì ... 6) Nếu có bất cứ điều gì khác xảy ra thì bạn đang nuốt lỗi.
GC.Collect()
là khi xử lý một số đối tượng COM.