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.32 Replies
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?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
If I check if there is an exception, it just prints that there is no issue
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.
i'm not sure why yours isn't working, because this works as expected
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
If the module returns a task, but has an uncaught exception, wouldn't it still break?
no, because now the continuation will observe the exception
nevermind, doesn't actually work unless it's properly async
Okay I think an issue might be that the async on my Initialize is an override
Maybe if I make the default Initialize an Async
if you have an async Initialize it will never be called through that interface
Well I was thinking of changing it to this
Wouldn't need the ContinueWith tho
if it's not actually async then there's no point in making the method async
Some are, some aren't
The default is void
Twitch moduel I am testing it with, that's async
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))
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
so where is your async overload?
how does it get called?
Oh wait
I just noticed it's async void
Sorry brain fart there
yeah, never use async void
Why is that though?
because when exceptions are thrown in async void methods there is no way to observe them
leading to this exact issue you're having
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
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
Well that explains why nothing I did from online worked. Thanks for the help
async void basically only exists to allow async code in event handlers and is the only place you should ever use it
Well I never actually expected Init to return anything
I didn't realize it was needed for catching a throw from a Task.Run
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
I see
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.
That's why .ContinueWith was returning immediately, because it wasn't getting task information back like progress?
right, there's no way for it to capture the result of the call
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:
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