My await is awaiting for the heat death of The Universe and beyond

Hello there, so I have method like this:
// fragment
var uevent = ReadIfExists(Path.Combine(dev, "uevent"));
var sizeIn512BlocksRaw = ReadIfExists(Path.Combine(dev, "size"));
var logicalBlockSizeRaw = ReadIfExists(Path.Combine(dev, "queue", "logical_block_size"));
var model = ReadIfExists(Path.Combine(dev, "device", "model"));
var roRaw = ReadIfExists(Path.Combine(dev, "ro"));

await Task.WhenAll(uevent, sizeIn512BlocksRaw, logicalBlockSizeRaw, model, roRaw).ConfigureAwait(false); // <- hangs here

var ueventData = _ueventParser.Parse(uevent.Result);
var sizeIn512Blocks = ulong.TryParse(sizeIn512BlocksRaw.Result, out var size) ? size : 0;
var logicalBlockSize = uint.TryParse(logicalBlockSizeRaw.Result, out var lbs) ? lbs : 0;
var ro = roRaw.Result == "1";
// fragment
var uevent = ReadIfExists(Path.Combine(dev, "uevent"));
var sizeIn512BlocksRaw = ReadIfExists(Path.Combine(dev, "size"));
var logicalBlockSizeRaw = ReadIfExists(Path.Combine(dev, "queue", "logical_block_size"));
var model = ReadIfExists(Path.Combine(dev, "device", "model"));
var roRaw = ReadIfExists(Path.Combine(dev, "ro"));

await Task.WhenAll(uevent, sizeIn512BlocksRaw, logicalBlockSizeRaw, model, roRaw).ConfigureAwait(false); // <- hangs here

var ueventData = _ueventParser.Parse(uevent.Result);
var sizeIn512Blocks = ulong.TryParse(sizeIn512BlocksRaw.Result, out var size) ? size : 0;
var logicalBlockSize = uint.TryParse(logicalBlockSizeRaw.Result, out var lbs) ? lbs : 0;
var ro = roRaw.Result == "1";
This is the code from Program.cs:
Application.Init();

try
{
var blkProvider = new LinuxBlkDeviceProvider();
var deviceList = await blkProvider.ListDevices(); // <- this hangs
// ...
Application.Init();

try
{
var blkProvider = new LinuxBlkDeviceProvider();
var deviceList = await blkProvider.ListDevices(); // <- this hangs
// ...
Good stuff. And in normal application it works, no problem at all. But I use Terminal.Gui and after Application.Init() it simply don't work. I heard about this SynchronizationContext stuff and what I've seen Terminal.Gui uses it. I also heard about this .ConfigureAwait(false) But it doesn't work! Application just hangs on this await. Those two won't worth either:
var deviceList = blkProvider.ListDevices().Result;
var deviceList = await Task.Run(async () => await blkProvider.ListDevices());
var deviceList = blkProvider.ListDevices().Result;
var deviceList = await Task.Run(async () => await blkProvider.ListDevices());
But then, this one works:
var deviceList = Task.Run(async () => await blkProvider.ListDevices()).Result;
var deviceList = Task.Run(async () => await blkProvider.ListDevices()).Result;
What is going on here? How I can fix it and use juicy async/await with Terminal.Gui? Is it me doing something wrong or just some bug in Terminal.Gui?
5 Replies
Tvde1
Tvde12mo ago
I'm not sure about this, but you could try:
var uevent = ReadIfExists(Path.Combine(dev, "uevent"))
+ .ConfigureAwait(false);
var sizeIn512BlocksRaw = ReadIfExists(Path.Combine(dev, "size"))
+ .ConfigureAwait(false);
var logicalBlockSizeRaw = ReadIfExists(Path.Combine(dev, "queue", "logical_block_size"))
+ .ConfigureAwait(false);
var model = ReadIfExists(Path.Combine(dev, "device", "model"))
+ .ConfigureAwait(false);
var roRaw = ReadIfExists(Path.Combine(dev, "ro"))
+ .ConfigureAwait(false);

await Task.WhenAll(uevent, sizeIn512BlocksRaw, logicalBlockSizeRaw, model, roRaw).ConfigureAwait(false);
var uevent = ReadIfExists(Path.Combine(dev, "uevent"))
+ .ConfigureAwait(false);
var sizeIn512BlocksRaw = ReadIfExists(Path.Combine(dev, "size"))
+ .ConfigureAwait(false);
var logicalBlockSizeRaw = ReadIfExists(Path.Combine(dev, "queue", "logical_block_size"))
+ .ConfigureAwait(false);
var model = ReadIfExists(Path.Combine(dev, "device", "model"))
+ .ConfigureAwait(false);
var roRaw = ReadIfExists(Path.Combine(dev, "ro"))
+ .ConfigureAwait(false);

await Task.WhenAll(uevent, sizeIn512BlocksRaw, logicalBlockSizeRaw, model, roRaw).ConfigureAwait(false);
canton7
canton72mo ago
That shouldn't make any difference whatsoever Those tasks aren't being awaited I'm betting that await blkProvider.ListDevices().ConfigureAwait(false) will work, but I'd want to understand what you're doing that Terminal.Gui doesn't like It looks like it might install a SynchronizationContext, which posts messages to the main loop which is run by Application.Run? I don't know how setting up a Terminal.Gui application runs, but I'm guessing you need to be calling Application.Run, and then posting a job to it to do your loading? I'm guessing by subscribing to either the InitializedChanged or NotifyNewRunState event, then using Application.Run? Application.MainLoop.Invoke(() => RunInitialization()); before Application.Run might do it too? Ah yes, Application.Init installs a SynchronizationContext: https://github.com/gui-cs/Terminal.Gui/blob/2be83972036020ce26794a63ad677abf1b54b65f/Terminal.Gui/App/ApplicationImpl.cs#L100 You will not want any awaits before Application.Init, or between Application.Init and Application.Run
ScuroGuardiano
ScuroGuardianoOP2mo ago
Oh alright, I see. Thanks guys ❤️
Tvde1
Tvde12w ago
though doing it with configure await is fine right
canton7
canton72w ago
The ConfigureAwaits you put in place in your sample won't make any difference You'll need them on every await inside ReadIfExists, and anything which they call, including inside library code. And you might not want to do that, because you might be relying on an await returning a a single-threaded context Much better to just play along with the threading model that Terminal.Gui wants to use, rather than making sure that every single little bit of your code is correctly configured to side-step it

Did you find this page helpful?