Would definitely like to see how to properly code for long running tasks, please! You are awesome and I sincerely appreciate your videos! Thank you for your time. Edit: No, I didn't know all the caveats, thank you for explaining the issue and describing why and how it all works.
If you mean not waiting for a result then you can just create a service with a queue and just queue your tasks there. The service will loop over the queue and execute the tasks in the background while the Add/Queue method immediately returns allowing you to not wait.
our organization had the need for distributed durable jobs; however, we didn't want. to be constraint to a cloud provider service. so we rolled our own solution and open sourced it. it uses mongo db to track job state and status and azure service bus. but those could be rotated out for any other infrastructure github/firebend/jobba you could also use something like hang fire or coravel
I learned about Task and exceptions the hard way. This video explains it really well! I'd love to get some info on long running tasks, as that is something I deal with at work every day. Thanks, Nick!
I'm always up for more videos on async stuff, since it's one of the most unintuitive areas in C#. On an unrelated note, I would also love to see some videos about unsafe coding. When it's needed or not, the pitfalls to lookout for and common practices to follow.
Having more info about long running tasks would be great. I often heard that you shouldn't create long running background tasks but never found a clear explanation why.
AFAIK thread pool is designed for exactly what the name stands for - the thread should do work fast and go back to the pool when it's done in order to get next item on "to do list". What you are doing with a long running task is you're stealing the thread from thread pool indefinitely. The thread is not really pooled anymore, right? It's better to start a dedicated thread that is not rented from the pool in the first place. Also you don't have control over thread pool threads e.g. should it be foreground or background etc.
Internally we create an extension method to tasks that requires an Action and optional Action for continuing with. Saw this from Brian Lagunas video on RUclips years back.
I almost didn't click on this one because I expected yet another (pedantic) rant on how async void hides the function from the exception stack trace and whatnot as seemingly everyone that talks about c# has done before. But this is actually highlighting a small but helpful and important detail that I didn't know about.
Great video! Saved me a headache, I'm coming from async/await in JS and then async/await in Python, now using this paradigm in C# and of course there's all these subtle differences in every language 🙄
As for extremely long running tasks however, I definitely recommend utilizing the PUB/SUB message queue methodologies. The subscriber model is truly a "fire and forget" and will completely release your threads. Thanks for another great video.
Very interesting, especially since we are forced by the class library to use async void in some places, it's great to know the gotchas. I hadn't realised how dangerous unhandled exceptions could be inside an async void function. For fire & forget I'd typically just receive the Task but not await it ( _ = DoThing() ), or use a message queue and handle it elsewhere, depending on what the task actually is. Yes, a video about long running threads would be great. I frequently use a thread to monitor external hardware and am still using the old Thread.Start() API, with a worker loop that does the hardware monitoring logic and a frequent Thread.Sleep(). This usually runs for the life of the application, and it works well, managing a job queue for the hardware, re-establishing connections, raising event etc. and is a very mature pattern for us. but I'm sure that in the new async world there's a more modern design pattern that I should be considering.
I would definitely like to see the long-running task version! The thing I do for fire and forget is just have a small helper method (called FireNForget) that takes a Func (func so it can capture both sync and async exceptions), and wraps everything in a try-catch(Exception ex), with the catch block (carefully!) logging the exception where it can be noted. FireNForget is very deliberately made short and simple enough that as little as possible is done outside the try-catch guard, and hasn't failed me. Does this seem like a good solution? Does it seem like there are any problems with this?
For one of the projects I work on I created a custom thread pool for tasks, since one executable handles multiple services. I created a FireAndForget method on it to deal with this specifically. I also added a function for long running tasks, which would create a named thread for the task to run on, so you can find it in the threads list easily.
There are so many nuances with Tasks to be mindful of. I already figured out exceptions not being caught in calling thread, but good to know about those solutions.
You can override async void in your code base to route exceptions to a logger instead of crashing the app. See the blog post "Extending the async methods in C#" by Sergey Tepliakov from 2018. Unfortunately, C# 10 async overrides don't support async void, so that's the only way to override it (will apply to the entire assembly).
Me too. In my project I have some long (permanently) running threads, which do some job, stores it in a dictionary, which the main thread uses (consumes) in intervals. I think there is some way to create it via tasks, but I think doing it via threads is more suitable. I remember using some enum which set it to a long running task. Is it true that one should preferably use tasks instead of threads directly?
public static class TaskExtension { public static async void FireAndForget(this Task task) { try { await task; } catch { } } } Same approach, just add .FireAndForget() to your async-method-call.
This is wildly different than what I show in the video and it's flawed. Don't use this. You should only deal with tasks that haven't completed or are faulted. Awaiting everything is wasteful. Also ConfigureAwait(false) is missing. Just use the example in the video.
I'd love to see a video on long running tasks, also. When one makes a threading mistake, it can be impossible to recreate or debug. The one thing I always do in a long running compute intensive task is to make sure it is going to yield periodically. Adding a periodic short Sleep() can make a process that's running 100% CPU become 'idle', since the processing is broken into such small timeframes interspersed with the Sleep.
Long running task (i consider long anything over a second) thats need to wait for results and perform in the background would be great to see your take on it.
@@diadetediotedio6918 I know this is wotking and TaskCreationOptions.LongRunning need also to be considered bit i want nick take on that. I tried to suggest this before in the clean consumer video he made but i guess i wasnt clear enough about my question
Since you mentioned it, I really would like a video on long running background tasks. I am rather worried about my use of QueueBackgroundWorkItem being a very improper way of doing it in IIS, and that IRegisteredObject with its own thread isn't quite right either. Except for maybe a file watcher, I can't really think of any client side uses for such...
I had attempted to use an async void before, needless to say, it blew up in my face but didn't crash the app. Learned my lesson the hard way to always use at least an untyped `async Task`...
I am using async void on a Blazor WebAssembly app. I am updating the UI every 5 seconds, so to configure the periodic timer I use async void inside the OnInitializedAsync event without awaiting it. Are there better ways to use background long running tasks on blazor wasm?
You could also use the ContinueWith method on the task and see if the thread is faulted then handle the exception?? However you would need to do it for every call to the asyn method 😔. Great video, thanks Nick 👍
I would love to hear your take on long running tasks. I've run into this multiple times in my career where we wanted to trigger a long running event off of an API Endpoint, and the controller eventually decides it's done with the fire and forget task and the task just stops.
What about '_ = bgTask().ContinueWith((t) => { if (t.IsFaulted) { // here we have t.Exception of type AggregateException, having all exceptions occured in background } });
I have often wondered how to safely handle longer running operations in a separate thread from a button event in a WinForms app. I would appreciate some advice on that. I typically mark the OnClick event as async, create a class for the long runner, new up the class and call the async method with an await and try catch in my OnClick. This video makes me think that my approach is way wrong.
I am working on a worker service that will on background and will scrape data continuously, and ofcourse the service will be running for long periods. Does worker service handles this case automatically?
I would love a vid about long running tasks too please. I'm working on a little robot that has a cam attacked and is supposed to constanly fetch and process the frames that come in via OpenCV. Currently I'm running the logic inside a Task.Run() but I guess that's not a good approach...
I really want to learn more about long running tasks, I've tried using them with a consumer system, but I ran into issues of objects not getting out of scope of my while loop and GC not collecting things ending up in a memory leak.
Why don't WPF have async Task Button1_Click handler instead? I think it's obvious to have this overload to avoid app crash... Anyway it's a very common scenario when you pass async task in the non-async functions, like Parallel.For/ForEach and it's transformed to async void
1. WFP and WinForms have their own synchronization context so it works a bit differently since it doesn't just put the exception on a random thread from the threadpool. 2. It doesn't really matter since the framework doesn't handle the exceptions in any meaningful way anyway so it'd just crash regardless.
Hi Nick! Thanks for the valuable videos :) After watching your video a question came to my mind ... I am wondering if it would be wise to do so: Task.Run(async () => await someAsyncMethod()).ContinueWith((task) => { ... }).Forget();
Please make the video about long running tasks. I have done this a thousend time and there dont seem to be any way which didnt cause problems at the one or antoher point... 🙈
@nick Do you think you could include how to handle AggregateExceptions in this same context? My try catch outside of a function that threw an aggregate exception never returned and crashed my program
@@AnotherFancyUser thous will help to to do some last things before crash like log the error but will not prevent the crash because your application is already lost the context
while I would be interested in how to fire and forget long running tasks, I'd be even more interested in examples where this would be useful. my gut feeling tells me that if you use long-running fire and forget tasks, your design is probably wrong. just a gut feeling, though, so I'd like to have that thought challenged 🤓
So in Blazor there is the EventCallback where you can only give an void-Method in. How should you avoid async void there? Is there any possibility? Just using void and start an task or what?
What do you mean you can only give a void method in? You mean the function you set on the component from outside, that get executed when the event callback is invoked? You can put an async Task method and it works, not just void.
the cost of catching exception in dotnet is told to be not so cheap. Sometimes, it will go easily over a millisecond and may impact on UI thread's refreshing the display. Along with async void, awaiting a task is conduit thru which the exception flows to the principal thread. What really matters is not letting worker thread's exception thrown to calling thread. If you make it sure, it doesn't matter you use either of async void or async Task.
I hate dealing with async in C# code. I feel like it was sold as a) a way of improving performance, b) a way of making the code easier to understand and organize. The actual reality seems to be a feature that makes it super easy for jimmy to write deadlocks and waste a huge amount of time.
@@pierwszywolnynick Suppressing the warning doesn't prevent the crash. [Edit] Sorry, UnobservedTaskException used to crash the process, but they changed the behavior in .Net 4.5.
With the power of static analyzers available in C# you need to close your eyes really hard and supress all warnings to screw this up :) Most of this video shows you what NOT to do, pretty much every confusing fringe scenario is covered and now you can easily avoid those mistakes. Most of the error handling is not specific to C# either, these quirks apply to any async-await language even though the behavior can differ slightly.
Would definitely like to see how to properly code for long running tasks, please! You are awesome and I sincerely appreciate your videos! Thank you for your time. Edit: No, I didn't know all the caveats, thank you for explaining the issue and describing why and how it all works.
If you mean not waiting for a result then you can just create a service with a queue and just queue your tasks there. The service will loop over the queue and execute the tasks in the background while the Add/Queue method immediately returns allowing you to not wait.
Also I think you could make the thread scheduling preemptive so that the queued task does not block a process forever
our organization had the need for distributed durable jobs; however, we didn't want. to be constraint to a cloud provider service.
so we rolled our own solution and open sourced it.
it uses mongo db to track job state and status and azure service bus.
but those could be rotated out for any other infrastructure
github/firebend/jobba
you could also use something like hang fire or coravel
For this you'd use a BackgroundService / Create your own IHostedService
@First Last You might want to look into things like Azure Service Bus.
I learned about Task and exceptions the hard way. This video explains it really well! I'd love to get some info on long running tasks, as that is something I deal with at work every day.
Thanks, Nick!
I'm always up for more videos on async stuff, since it's one of the most unintuitive areas in C#.
On an unrelated note, I would also love to see some videos about unsafe coding. When it's needed or not, the pitfalls to lookout for and common practices to follow.
Having more info about long running tasks would be great. I often heard that you shouldn't create long running background tasks but never found a clear explanation why.
AFAIK thread pool is designed for exactly what the name stands for - the thread should do work fast and go back to the pool when it's done in order to get next item on "to do list". What you are doing with a long running task is you're stealing the thread from thread pool indefinitely. The thread is not really pooled anymore, right? It's better to start a dedicated thread that is not rented from the pool in the first place. Also you don't have control over thread pool threads e.g. should it be foreground or background etc.
6:00 - good point about Func and Action
The Action silent void is definitely a gotcha to keep a lookout for
Internally we create an extension method to tasks that requires an Action and optional Action for continuing with. Saw this from Brian Lagunas video on RUclips years back.
Yeah, video about long running Tasks would be helpful!
I really love videos on C# asynchronous programming model, it's so elegant the Task-Based approach🤘😍
I almost didn't click on this one because I expected yet another (pedantic) rant on how async void hides the function from the exception stack trace and whatnot as seemingly everyone that talks about c# has done before. But this is actually highlighting a small but helpful and important detail that I didn't know about.
Great video! Saved me a headache, I'm coming from async/await in JS and then async/await in Python, now using this paradigm in C# and of course there's all these subtle differences in every language 🙄
As for extremely long running tasks however, I definitely recommend utilizing the PUB/SUB message queue methodologies. The subscriber model is truly a "fire and forget" and will completely release your threads.
Thanks for another great video.
yes pleeease, 2 things i really need to understand:
1. long running background tasks.
2. .ConfigureAwait(false)
Very interesting, especially since we are forced by the class library to use async void in some places, it's great to know the gotchas. I hadn't realised how dangerous unhandled exceptions could be inside an async void function.
For fire & forget I'd typically just receive the Task but not await it ( _ = DoThing() ), or use a message queue and handle it elsewhere, depending on what the task actually is.
Yes, a video about long running threads would be great. I frequently use a thread to monitor external hardware and am still using the old Thread.Start() API, with a worker loop that does the hardware monitoring logic and a frequent Thread.Sleep(). This usually runs for the life of the application, and it works well, managing a job queue for the hardware, re-establishing connections, raising event etc. and is a very mature pattern for us. but I'm sure that in the new async world there's a more modern design pattern that I should be considering.
Thank you for collecting this info in one place. This is exactly what I was curious about. Great info. I'd love that long running tasks video!
Yes please Nick, would really like to learn more about long running background tasks. Especially with regards to desktop\WPF\WinForms
I would definitely like to see the long-running task version!
The thing I do for fire and forget is just have a small helper method (called FireNForget) that takes a Func (func so it can capture both sync and async exceptions), and wraps everything in a try-catch(Exception ex), with the catch block (carefully!) logging the exception where it can be noted. FireNForget is very deliberately made short and simple enough that as little as possible is done outside the try-catch guard, and hasn't failed me. Does this seem like a good solution? Does it seem like there are any problems with this?
Good video! And yes, long running background tasks please 🙏
Thank you Nick! Yes, more async vids!
For one of the projects I work on I created a custom thread pool for tasks, since one executable handles multiple services. I created a FireAndForget method on it to deal with this specifically. I also added a function for long running tasks, which would create a named thread for the task to run on, so you can find it in the threads list easily.
yayy new nick upload!
There are so many nuances with Tasks to be mindful of. I already figured out exceptions not being caught in calling thread, but good to know about those solutions.
nick:
your code could be in danger.
my code:
I'm not in danger, I am the danger skyler.
You can override async void in your code base to route exceptions to a logger instead of crashing the app. See the blog post "Extending the async methods in C#" by Sergey Tepliakov from 2018.
Unfortunately, C# 10 async overrides don't support async void, so that's the only way to override it (will apply to the entire assembly).
Nick we need a video on long running background tasks - preferably in the realm of http requests etc
Hi Nick, Thank you for the great video and explanations.
I would be very interested in a long background runner tasks video :)
Me too. In my project I have some long (permanently) running threads, which do some job, stores it in a dictionary, which the main thread uses (consumes) in intervals. I think there is some way to create it via tasks, but I think doing it via threads is more suitable. I remember using some enum which set it to a long running task.
Is it true that one should preferably use tasks instead of threads directly?
public static class TaskExtension
{
public static async void FireAndForget(this Task task)
{
try { await task; }
catch { }
}
}
Same approach, just add .FireAndForget() to your async-method-call.
This is wildly different than what I show in the video and it's flawed. Don't use this. You should only deal with tasks that haven't completed or are faulted. Awaiting everything is wasteful. Also ConfigureAwait(false) is missing. Just use the example in the video.
@@nickchapsas Thanks Nick!
I'd love to see a video on long running tasks, also. When one makes a threading mistake, it can be impossible to recreate or debug.
The one thing I always do in a long running compute intensive task is to make sure it is going to yield periodically. Adding a periodic short Sleep() can make a process that's running 100% CPU become 'idle', since the processing is broken into such small timeframes interspersed with the Sleep.
Thanks for this informative video Nick! Question, what do you consider short running and long running? Is > 3 seconds long and < 3 seconds short?
Long running task (i consider long anything over a second) thats need to wait for results and perform in the background would be great to see your take on it.
Task.Factory.StartNew(..., ...LongRunning)
@@diadetediotedio6918 I know this is wotking and TaskCreationOptions.LongRunning need also to be considered bit i want nick take on that. I tried to suggest this before in the clean consumer video he made but i guess i wasnt clear enough about my question
I agree with others... I would like to see a video on long-running background tasks
Since you mentioned it, I really would like a video on long running background tasks.
I am rather worried about my use of QueueBackgroundWorkItem being a very improper way of doing it in IIS, and that IRegisteredObject with its own thread isn't quite right either.
Except for maybe a file watcher, I can't really think of any client side uses for such...
I had attempted to use an async void before, needless to say, it blew up in my face but didn't crash the app.
Learned my lesson the hard way to always use at least an untyped `async Task`...
Hi Nick,
Very nice video. Am more interested on long running backgroundTask video
i would love to see more about async programming in c# :)
Long running background tasks… yes, interested!
I stopped using async void ever since your first video warning about it's potential dangers
I am using async void on a Blazor WebAssembly app. I am updating the UI every 5 seconds, so to configure the periodic timer I use async void inside the OnInitializedAsync event without awaiting it. Are there better ways to use background long running tasks on blazor wasm?
In MAUI I use MainThread.InvokeOnMainThreasAsync
Use PeriodicTimer, you can do
while(await timer.WaitNextTickAsync())
await callback()
Or something like this?
@@ghevisartor6005 I am doing this but to make the loop run in background I wrap that logic on a async void method
@@keke772 also i dont know really about webassembly but you have to InvokeAsync in Blazor server if you update the Ui from another thread ofc.
What is the difference between
bgTask.RunAsync()
and
Task.Run(async => await bgTask.RunAsync())
?
//crash
catch (Exception ex)
{
Console.WriteLine(ex);
}
//Don`t crash
catch (Exception ex)
{
Console.WriteLine(ex.Message);
//don`t crash and show a message
}
or
catch (Exception ex)
{
Debug.Print(ex.Message);
}
Hi Nick! Great content as always!
it really does my head in all these different ways that async methods deal with exceptions.
Yes, please made a video on Long running operations!
Hi, in fact I didn't know about async void, always used async Task. Thanks.
You could also use the ContinueWith method on the task and see if the thread is faulted then handle the exception?? However you would need to do it for every call to the asyn method 😔.
Great video, thanks Nick 👍
Would love to see how you handle Long running tasks. Thanks
Can u make a tutorial/video about „exception handling best practices“?
Definitely want a video on long running background tasks
Thanks for reminding ForEach with Action of T.
Do you plan to make zero to hero about all there is to know about threads? Threads, Tasks in general are very useful but can be tricky.
What'd be an alternative when working with Windows Forms?
Hi Nick, thank you for this video. What is the behavior when using the discard '_ =' instead 'await' in these scenarios?
Great content as always.
Very interesting video as always.
I'm interested too with long background runner taskas
I'm wondering how Windows forms require button click events to return void, even when they are async.
FireAndForget == Thread.
From my point of view.
At least for long-running tasks
Would be great to have a video on long running background tasks which can run in parallel and start manually from different controllers
I would love to hear your take on long running tasks. I've run into this multiple times in my career where we wanted to trigger a long running event off of an API Endpoint, and the controller eventually decides it's done with the fire and forget task and the task just stops.
What about '_ = bgTask().ContinueWith((t) => { if (t.IsFaulted) { // here we have t.Exception of type AggregateException, having all exceptions occured in background } });
Rolls off the tongue
I also like it better. Task.Run() is pretty overused imho
I have often wondered how to safely handle longer running operations in a separate thread from a button event in a WinForms app. I would appreciate some advice on that. I typically mark the OnClick event as async, create a class for the long runner, new up the class and call the async method with an await and try catch in my OnClick. This video makes me think that my approach is way wrong.
Hi, Nick. Can you make course about grpc in dotnet?
I am working on a worker service that will on background and will scrape data continuously, and ofcourse the service will be running for long periods. Does worker service handles this case automatically?
Definitely make a video on longer running tasks 👍
I have never used asynch void. I have never even thought of using this.
Good content. A remark for improvement: Speak slowly, it will help and improve the message delivery.
Hi, it will be pretty cool if you`ll make video about concurrent collections.
There not so many videos about them on yt
This behaviour is seen in many languages 🙂
Asynchronous things are always complicated.
I would love a vid about long running tasks too please.
I'm working on a little robot that has a cam attacked and is supposed to constanly fetch and process the frames that come in via OpenCV.
Currently I'm running the logic inside a Task.Run() but I guess that's not a good approach...
Oh learned this the hard way. In production xD
I really want to learn more about long running tasks, I've tried using them with a consumer system, but I ran into issues of objects not getting out of scope of my while loop and GC not collecting things ending up in a memory leak.
Why don't WPF have async Task Button1_Click handler instead? I think it's obvious to have this overload to avoid app crash... Anyway it's a very common scenario when you pass async task in the non-async functions, like Parallel.For/ForEach and it's transformed to async void
Probably because WPF is utter trash and always has been.
1. WFP and WinForms have their own synchronization context so it works a bit differently since it doesn't just put the exception on a random thread from the threadpool.
2. It doesn't really matter since the framework doesn't handle the exceptions in any meaningful way anyway so it'd just crash regardless.
Hi Nick! Thanks for the valuable videos :) After watching your video a question came to my mind ... I am wondering if it would be wise to do so: Task.Run(async () => await someAsyncMethod()).ContinueWith((task) => { ... }).Forget();
Please make the video about long running tasks. I have done this a thousend time and there dont seem to be any way which didnt cause problems at the one or antoher point... 🙈
Great video, please do long running
Usually in Task.Run(t => task). I would like to see long running task video.
Please do a video on long-running background tasks!
@nick Do you think you could include how to handle AggregateExceptions in this same context? My try catch outside of a function that threw an aggregate exception never returned and crashed my program
CC: "hello everybody I'm naked in this video"
Well, that was a lie. :(
I'd like to see the long running task video as well
of cource you MUST using try-catch where possible something can goes wrong! its a base
Would global error handler prevent from application crash?
Some sort of middleware? that is a good question.
@@AnotherFancyUser Even in console app: AppDomain.CurrentDomain.UnhandledException. I prefer never use try/catch in my code.
@@AnotherFancyUser thous will help to to do some last things before crash like log the error but will not prevent the crash because your application is already lost the context
while I would be interested in how to fire and forget long running tasks, I'd be even more interested in examples where this would be useful. my gut feeling tells me that if you use long-running fire and forget tasks, your design is probably wrong. just a gut feeling, though, so I'd like to have that thought challenged 🤓
I would like to see a video about long running tasks
i can not buy your courses because i have 4 digits pin on the card, but it allows only 3 to enter ) something wrong ) @nick
So in Blazor there is the EventCallback where you can only give an void-Method in. How should you avoid async void there? Is there any possibility? Just using void and start an task or what?
What do you mean you can only give a void method in? You mean the function you set on the component from outside, that get executed when the event callback is invoked?
You can put an async Task method and it works, not just void.
@@ghevisartor6005 Oh... Damn I don't know why I had problems with it... You are absolutely right, I tried it. Sorry ;-)
@@Yannici no problem sometimes it's even the compiler giving false errors
is there any cons in using async void if we were to put try catch in it? any performance impact or whatsoever?
the cost of catching exception in dotnet is told to be not so cheap. Sometimes, it will go easily over a millisecond and may impact on UI thread's refreshing the display.
Along with async void, awaiting a task is conduit thru which the exception flows to the principal thread.
What really matters is not letting worker thread's exception thrown to calling thread. If you make it sure, it doesn't matter you use either of async void or async Task.
Just trying consumes almost nothing, but the catching part is actually the problem
The method knows what it is doing, but not why this is done. This might prevent meaningful handling of the exception.
Another request for covering long running tasks
I hate dealing with async in C# code. I feel like it was sold as a) a way of improving performance, b) a way of making the code easier to understand and organize. The actual reality seems to be a feature that makes it super easy for jimmy to write deadlocks and waste a huge amount of time.
What do you do when you have no other choice (like in unity?)
I explained that in the WPF section
I'm appalled the compiler even allows async void. I would never write something like that intentionally.
Do a Video on long running background
Long-running background task!!
Thanks, now i know that i did some bugs 🙂
Wait, list.ForEach is a thing?...
What about using a discard? `_ = SomeTask();` for example.
That doesn't prevent UnobservedTaskException, that just tells the C# analyzer to stop warning you about it.
@@pierwszywolnynick Suppressing the warning doesn't prevent the crash.
[Edit] Sorry, UnobservedTaskException used to crash the process, but they changed the behavior in .Net 4.5.
Long running tasks!!
public static async void SafeAwait(this Task task, Action handleException)
{
try
{
await task;
}
catch (Exception e)
{
handleException?.Invoke(e);
}
}
C#'s asynchronous-y has so many confusing ifs that makes it bad
Generally bad? No.
Somewhat tricky and with many pitfalls? Yes.
Nah, it's actually pretty good and powerfull.
In general you will never need to worry about these problems if you do structure your code correctly
With the power of static analyzers available in C# you need to close your eyes really hard and supress all warnings to screw this up :) Most of this video shows you what NOT to do, pretty much every confusing fringe scenario is covered and now you can easily avoid those mistakes. Most of the error handling is not specific to C# either, these quirks apply to any async-await language even though the behavior can differ slightly.
Ahh Tasks, thank God for Stephen Cleary many years ago.
sometimes async void is good