C
C#3mo ago
Moonlit

Can I catch an error from Task.Run to stop threads crashing main program?

I have a bot that is meant to support multiple platforms like (Twitch, Discord, Spotify, Etc). I do this by loading them all as .DLL files. I've tried my hardest to make sure that any errors in commands from modules doesn't crash the bot, but the issue is that if the Task Initializing has an error, it would cause the main program to crash because the Try/Catch that starts it doesn't work because it's on a separate thread I assume? Anyway I know I could catch an error in each modules Initializing method to prevent this and to end the task gracefully, but I'd like to make the bot impervious to being crashes by any modules. I don't want to rely on the modules themselves to do it. Is that possible? If so then how? PS: Line 46 is where I do it and ignore // RunCommand not accessible I fixed that.
No description
32 Replies
Jimmacle
Jimmacle3mo ago
you can handle the exception yourself in a continuation .ContinueWith(...), but is there a reason this is being started as a task (and not waiting for it to complete) in the first place?
Moonlit
MoonlitOP3mo ago
Because of the networking aspect of some modules. For instance Twitch module might make some calls to the server to make sure it can connect properly and I didn't want to lock the whole program down if a module is waiting How exactly do I implement the ContinueWith? Because it seems that doesn't wait for the task to finish
Moonlit
MoonlitOP3mo ago
If I check if there is an exception, it just prints that there is no issue
No description
Moonlit
MoonlitOP3mo ago
[Twitch] [Initialize] Starting Twitch Module.
[Bot] [<Execute>b__2_1] Module loaded with no issues
Unhandled exception. System.Exception: Testing module failure. Twitch throwing exception
at Modules.Twitch.Twitch.Initialize(Func`3 commandRunner)
at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_1(Object state)
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()

Process finished with exit code -532,462,766.
[Twitch] [Initialize] Starting Twitch Module.
[Bot] [<Execute>b__2_1] Module loaded with no issues
Unhandled exception. System.Exception: Testing module failure. Twitch throwing exception
at Modules.Twitch.Twitch.Initialize(Func`3 commandRunner)
at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_1(Object state)
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()

Process finished with exit code -532,462,766.
When I look online people will create the task as a variable and await for it.. Which defeats the point of the task really. I guess I could make the load module commands a task itself and then that could await the results of the module it's loading? Unless you know how I can do that inside the .ContinueWith? I wouldn't need the Initialize to be a task then I guess... Maybe that is a more logical way of doing it? If I want to make sure the core bot is impervious? Because the thread would be from the bot rather than from the module, because I know my loading method won't crash but I don't know if a module will.
Jimmacle
Jimmacle3mo ago
i'm not sure why yours isn't working, because this works as expected
Task.Run(async () =>
{
await Task.Delay(1000);
throw new Exception();
}).ContinueWith(x =>
{
if (x.Exception is not null)
Console.WriteLine("exception");
});
Console.ReadLine();
Task.Run(async () =>
{
await Task.Delay(1000);
throw new Exception();
}).ContinueWith(x =>
{
if (x.Exception is not null)
Console.WriteLine("exception");
});
Console.ReadLine();
looks like it only works if the code being run returns a task itself looks like your options are put a try/catch in the lambda or make it return a task and use ContinueWith
Moonlit
MoonlitOP3mo ago
If the module returns a task, but has an uncaught exception, wouldn't it still break?
Jimmacle
Jimmacle3mo ago
no, because now the continuation will observe the exception nevermind, doesn't actually work unless it's properly async
Moonlit
MoonlitOP3mo ago
Okay I think an issue might be that the async on my Initialize is an override
public interface IModule
{
void Initialize(Func<User, string, (string?, bool)> commandRunner);
void Shutdown();
}
public interface IModule
{
void Initialize(Func<User, string, (string?, bool)> commandRunner);
void Shutdown();
}
Maybe if I make the default Initialize an Async
Jimmacle
Jimmacle3mo ago
if you have an async Initialize it will never be called through that interface
Moonlit
MoonlitOP3mo ago
Well I was thinking of changing it to this
No description
Moonlit
MoonlitOP3mo ago
Wouldn't need the ContinueWith tho
Jimmacle
Jimmacle3mo ago
if it's not actually async then there's no point in making the method async
Moonlit
MoonlitOP3mo ago
Some are, some aren't The default is void Twitch moduel I am testing it with, that's async
Jimmacle
Jimmacle3mo ago
then that's a bug in your code, because this will never call the async one if moduleInstance is IModule then it's calling the sync method 100% of the time you'll have to make IModule's Initialize return a Task and then call Task.Run(async () => await moduleInstance.Initialize(runCommand))
Moonlit
MoonlitOP3mo ago
It does though? The Twitch module connected and it all worked when I ran it before, it awaits for messages correctly and etc. And they all come from that IModule which is void
No description
Jimmacle
Jimmacle3mo ago
so where is your async overload? how does it get called?
Moonlit
MoonlitOP3mo ago
Oh wait I just noticed it's async void Sorry brain fart there
Jimmacle
Jimmacle3mo ago
yeah, never use async void
Moonlit
MoonlitOP3mo ago
Why is that though?
Jimmacle
Jimmacle3mo ago
because when exceptions are thrown in async void methods there is no way to observe them leading to this exact issue you're having
Moonlit
MoonlitOP3mo ago
Yeh all my tasks inside of Twitch that are Async are tasks Okay I get you, that makes sense Yeh I need to make Initialize return a task
Jimmacle
Jimmacle3mo ago
it sounds like you want to make your initialize method return a task so modules that need async initialization can do it properly and ones that don't can just return Task.CompletedTask
Moonlit
MoonlitOP3mo ago
Well that explains why nothing I did from online worked. Thanks for the help
Jimmacle
Jimmacle3mo ago
async void basically only exists to allow async code in event handlers and is the only place you should ever use it
Moonlit
MoonlitOP3mo ago
Well I never actually expected Init to return anything I didn't realize it was needed for catching a throw from a Task.Run
Jimmacle
Jimmacle3mo ago
it's not about that, it's about how the async model works async code always returns a Task representing the state of the async call there's a bunch of behind the scenes magic that turns an async call into a state machine and the task is how it keeps track of its state
Moonlit
MoonlitOP3mo ago
I see
Jimmacle
Jimmacle3mo ago
Stephen Toub - MSFT
.NET Blog
How Async/Await Really Works in C# - .NET Blog
Async/await was added to the C# language over a decade ago and has transformed how we write scalable code for .NET. But how does it really work? In this post, we take a deep dive into its internals.
Moonlit
MoonlitOP3mo ago
That's why .ContinueWith was returning immediately, because it wasn't getting task information back like progress?
Jimmacle
Jimmacle3mo ago
right, there's no way for it to capture the result of the call
Moonlit
MoonlitOP3mo ago
I had the general idea that Async allowed waiting for an external thing to finish but I didn't fully know how it achieved it. Well you fixed my issue and I have a lot better understanding of it! So thanks again, I appreciate you :Moon_JJ_Love:
canton7
canton72mo ago
I strongly recommend never touching ContinueWith these days. It's a deceptively simple method which hides a lot of complexity (and rapidly becomes overwhelming once you start getting into the nuance). There's almost nothing that ContinueWith can do that you can't express more obviously and cleanly with async/await "never use async void" is bad advice too. Sometimes it's the best tool for the job. But because people keep repeating that stupid "never use async void" mantra, they end up with all sorts of hacky alternatives which are objectively worse in every way
Want results from more Discord servers?
Add your server