C
C#โ€ข3y ago
Doombox

JsonSerializer.DeserializeAsync() randomly hangs indefinitely [Answered]

private static async Task<T> TryReadDataFromFile<T>(string basePath) where T : class
{
var filepath = GetFilePath<T>(basePath);
if (!File.Exists(filepath)) return null;
await using var filestream = File.Open(filepath, FileMode.Open);
var res = await JsonSerializer.DeserializeAsync<T>(filestream); // This line
await filestream.DisposeAsync();
return res;
}
private static async Task<T> TryReadDataFromFile<T>(string basePath) where T : class
{
var filepath = GetFilePath<T>(basePath);
if (!File.Exists(filepath)) return null;
await using var filestream = File.Open(filepath, FileMode.Open);
var res = await JsonSerializer.DeserializeAsync<T>(filestream); // This line
await filestream.DisposeAsync();
return res;
}
System.Text.Json's DeserializeAsync seems to (entirely at random) decide to hang indefinitely, throws no exceptions or anything in the process which is odd, it's always the same file in AppData that causes it to hang.
45 Replies
Doombox
DoomboxOPโ€ข3y ago
I know that it's not necessary to dispose of the filestream explicitly but I was trying anything to make sure it wasn't locking the file for whatever reason
Up
Upโ€ข3y ago
unrelated, but you do not need to manually call filestream.DisposeAsync(). that is what that await using automatically does for you
Doombox
DoomboxOPโ€ข3y ago
^^ ๐Ÿ˜‰
Up
Upโ€ข3y ago
yeah I had already typed it out ;p
Trinitek
Trinitekโ€ข3y ago
what happens when you read the file in, and then deserialize it
Up
Upโ€ข3y ago
also what explicit type are you passing it
Doombox
DoomboxOPโ€ข3y ago
would have to move to synchronous, if that's the move then I'll just make the whole thing synchronous and wrap it in a Task.Run I guess, as for the object it's just a POCO
[Filename("Global.cfg")]
public class GlobalConfig : ConfigBase
{
public double Width { get; set; }
public double Height { get; set; }
public double Left { get; set; }
public double Top { get; set; }
public WindowState State { get; set; }
public bool LightTheme { get; set; }
public bool Use24HourClock { get; set; }

public GlobalConfig()
{
Width = 1100;
Height = 800;
Left = SystemParameters.PrimaryScreenWidth / 2 - Width / 2;
Top = SystemParameters.PrimaryScreenHeight / 2 - Height / 2;
State = WindowState.Normal;
LightTheme = false;
Use24HourClock = true;
}
}
[Filename("Global.cfg")]
public class GlobalConfig : ConfigBase
{
public double Width { get; set; }
public double Height { get; set; }
public double Left { get; set; }
public double Top { get; set; }
public WindowState State { get; set; }
public bool LightTheme { get; set; }
public bool Use24HourClock { get; set; }

public GlobalConfig()
{
Width = 1100;
Height = 800;
Left = SystemParameters.PrimaryScreenWidth / 2 - Width / 2;
Top = SystemParameters.PrimaryScreenHeight / 2 - Height / 2;
State = WindowState.Normal;
LightTheme = false;
Use24HourClock = true;
}
}
nothing too wild
Up
Upโ€ข3y ago
would have to move to synchronous
no, what for
Doombox
DoomboxOPโ€ข3y ago
the async method doesn't have an overload for strings, only for streams
Trinitek
Trinitekโ€ข3y ago
MemoryStream
Doombox
DoomboxOPโ€ข3y ago
that's true, didn't think of that
Trinitek
Trinitekโ€ข3y ago
if it locks up when you read it into a MemoryStream, it might be a file problem
Up
Upโ€ข3y ago
also please replace that enum with a string. makes it a lot more user-friendly than magic numbers in the config
Doombox
DoomboxOPโ€ข3y ago
await using var fs = File.Open(filepath, FileMode.Open, FileAccess.Read);
using var ms = new MemoryStream();
await fs.CopyToAsync(ms);
await using var fs = File.Open(filepath, FileMode.Open, FileAccess.Read);
using var ms = new MemoryStream();
await fs.CopyToAsync(ms);
yeah CopyToAsync into a memorystream also causes a lockup I switched file access just to be sure it wasn't trying to write for whatever reason as well, also a meaningless change but yeah for some reason I'm having (seemingly entirely at random) a file issue
Up
Upโ€ข3y ago
what if you use File.OpenRead() instead of Open()? as in await using var stream = File.OpenRead(path);
Doombox
DoomboxOPโ€ข3y ago
private static async Task WriteDataToFile<T>(string basePath, T data, bool writeIndented = false) where T : class
{
var filepath = GetFilePath<T>(basePath);
await using var filestream = File.Create(filepath);
var res = JsonSerializer.SerializeAsync(filestream, data, writeIndented ? IndentedJsonOptions : DefaultJsonOptions);
await filestream.DisposeAsync();
await res;
}
private static async Task WriteDataToFile<T>(string basePath, T data, bool writeIndented = false) where T : class
{
var filepath = GetFilePath<T>(basePath);
await using var filestream = File.Create(filepath);
var res = JsonSerializer.SerializeAsync(filestream, data, writeIndented ? IndentedJsonOptions : DefaultJsonOptions);
await filestream.DisposeAsync();
await res;
}
interesting, I'm also experiencing a lockup on filestream.DisposeAsync() here
Trinitek
Trinitekโ€ข3y ago
async deadlock? what kind of app is this
Doombox
DoomboxOPโ€ข3y ago
WPF
Trinitek
Trinitekโ€ข3y ago
add ConfigureAwait(false) after all your calls
Doombox
DoomboxOPโ€ข3y ago
uh this seems to have moved the deadlock somewhere else entirely, interesting do I have to ConfigureAwait(false) everything?
Trinitek
Trinitekโ€ข3y ago
pretty much, yes
Unknown User
Unknown Userโ€ข3y ago
Message Not Public
Sign In & Join Server To View
Trinitek
Trinitekโ€ข3y ago
ConfigureAwait(TRUE) is the default, which will try to run the continuation on the original thread
Doombox
DoomboxOPโ€ข3y ago
I'm only using a single GetAwaiter().GetResult() call so I just need to make sure that the entire chain that comes from that has ConfigureAwait(false) then?
D.Mentia
D.Mentiaโ€ข3y ago
I have never had to use ConfigureAwait, I find it a bit sus. It'd just be moving the deadlock to another thread
Doombox
DoomboxOPโ€ข3y ago
everything else is awaited it's really annoying that there's no way to really debug deadlocks, finding them when large parts of my codebase is async is a nightmare
D.Mentia
D.Mentiaโ€ข3y ago
oh, well, I've never used GetAwaiter .GetResult either... why do you need that? And also, not an answer to your question at all but... what's wrong with just File.ReadAllTextAsync ๐Ÿ˜›
Unknown User
Unknown Userโ€ข3y ago
Message Not Public
Sign In & Join Server To View
Doombox
DoomboxOPโ€ข3y ago
registering the factory in the container for my global config which everything relies on has to be synchronous so I'm stuck with it unfortunately, I could maybe rethink my architecture and make that async but it'd be a big headache
Trinitek
Trinitekโ€ข3y ago
okay, so the real reason why you're deadlocking is because the async library functions you're calling into are also using ConfigureAwait(false) which means the continuations can run on different threads. When you do GetResult() directly, you could potentially be doing that on the non-UI thread, which results in your deadlock the solution is... (drum roll please)
Doombox
DoomboxOPโ€ข3y ago
as for this, System.Text.Json's async methods are for streams only, no strings
Up
Upโ€ข3y ago
also the fact that you now have to read the entire file at once, rather than letting the serializer read it as-needed
Unknown User
Unknown Userโ€ข3y ago
Message Not Public
Sign In & Join Server To View
Trinitek
Trinitekโ€ข3y ago
GitHub
AspNetCoreDiagnosticScenarios/AsyncGuidance.md at master ยท davidfow...
This repository has examples of broken patterns in ASP.NET Core applications - AspNetCoreDiagnosticScenarios/AsyncGuidance.md at master ยท davidfowl/AspNetCoreDiagnosticScenarios
Trinitek
Trinitekโ€ข3y ago
read that part about deadlocks
D.Mentia
D.Mentiaโ€ข3y ago
lol, that's always the answer. Can we just remove Task.Result in the next version ๐Ÿ˜‰
Doombox
DoomboxOPโ€ข3y ago
yeah I think I'll rethink my architecture a bit and allow for the global config to be loaded later on, that way I can use async properly and avoid constructors
Trinitek
Trinitekโ€ข3y ago
either that^, or cheat and throw it into a Task.Run()
D.Mentia
D.Mentiaโ€ข3y ago
It's ugly but I would sort of recommend just, an async Initialize function where you need it
Doombox
DoomboxOPโ€ข3y ago
yeah that's (more or less) what I'm doing for a lot of other stuff already but the global config is a bit sketchier, a fair bit of code is reliant on it being initialised when it's constructed, but I can just rework everything so it's more tolerant handy to know that GetAwaiter().GetResult() can cause deadlocks though, was never really that aware of the inner workings, cheers all
Trinitek
Trinitekโ€ข3y ago
async is nasty business
Unknown User
Unknown Userโ€ข3y ago
Message Not Public
Sign In & Join Server To View
D.Mentia
D.Mentiaโ€ข3y ago
https://stackoverflow.com/questions/8145479/can-constructors-be-async the top answer here looks like a pretty neat solution, though IDK if it can be registered with DI like that
Doombox
DoomboxOPโ€ข3y ago
yeah the IoC container isn't hugely fond of basically any of the TPL, I'll just have to write it so the ViewModels can live without the config until I'm in an async context
Accord
Accordโ€ข3y ago
โœ… This post has been marked as answered!
Want results from more Discord servers?
Add your server