I've got the following class:
public class RecentLoginsFileRepository : IRecentLoginsRepository
{
private static readonly string _relativeFilePath = "Data\\recent_accounts.json";
private string _fullFilePath;
public RecentLoginsFileRepository()
{
_fullFilePath = Path.Combine(Environment.CurrentDirectory, _relativeFilePath);
Debug.WriteLine("Recent logins file path: " + _fullFilePath);
}
public async Task<IEnumerable<RecentAccount>> GetRecentLogins()
{
Debug.WriteLine("Calling " + nameof(GetRecentLogins));
if ( ! File.Exists(_fullFilePath))
{
return Enumerable.Empty<RecentAccount>();
}
using (FileStream fileStream = File.OpenRead(_fullFilePath))
{
List<RecentAccount>? accounts = await JsonSerializer.DeserializeAsync<List<RecentAccount>>(fileStream);
if(accounts == null)
{
Debug.WriteLine("Loaded recent accounts from file, but deserializing failed.");
return Enumerable.Empty<RecentAccount>();
}
else
{
return accounts;
}
}
}
public async Task SaveRecentLogins(IEnumerable<RecentAccount> recentLogins)
{
string json = JsonSerializer.Serialize(recentLogins, recentLogins.GetType());
if(json.Length > (1024 * 1024)) // File content would be > 1 MB
{
throw new Exception("File would be too large.");
}
Debug.WriteLine("Calling " + nameof(SaveRecentLogins));
using (FileStream fileStream = File.OpenWrite(_fullFilePath))
{
await File.WriteAllTextAsync(_fullFilePath, json);
}
}
}
Usages of the class (pseudo):
- A ViewModel calls
GetRecentLogins()to pass the data to the View to show a list of recently used accounts. - The user clicks on a recent account in the View. (Not important for this question, but to give some context).
- The code that processes logins, calls
GetRecentLogins()to get the list in memory there. Thus actuallyGetRecentLogins()is called again on another place in the application, just to get the data in memory there, since I don't store it 'globally' accessible in RAM. - The data in the loaded list of recent accounts is changed (like changing last login data on a
RecentAccountinstance in that list). - The method
SaveRecentLogins(updatedList)is called.
Or to make it simpler:
The method GetRecentLogins() is called multiple times.
The method SaveRecentLogins() is called one time.
The point is, if you think about it logically, both functions could be called multiple times and in any order without causing any problems. Or am I missing something? Does it have anything to do with it being implemented as async?
As soon as SaveRecentLogins() is called, I get the typical IOException, with the message: The process cannot access the file 'C:\foo\bar\baz\recent_accounts_2.json' because it is being used by another process.
My intention is that the functions are written in such a way that actions with the file are isolated. By that I mean that opening, reading/writing and closing are all done one after the other in their own methods (GetRecentLogins and GetRecentLogins). There's no reference kept to a stream object that could retain it for example.
Through any possible execution path in one of my class methods, the filestream would be closed (using blocks). As far as I can understand, my code should never allow another filestream to stay open.
I understand that it is a "process" working on the file. So that could also be a second instance of my program that is still running, or another program with which the file is still open. However, this is not the case. Every time I run a debug, only 1 instance of my program is running. There are no other programs with which I do anything with the recent_accounts.json file (so no word processor/notepad open).
I also tried creating a new file on a totally different path, and changed _relativeFilePath to that new path. But the behavior remains the same.
Sharing my whole code is impractical.