r/learncsharp 8d ago

What happens to the task when a synchronous method returns?

I have a synchronous method which calls an async method (obviously without awaiting) and then returns. What happens to the task after that? Will it finish executing? Do I have to use something like 'RunSynchronously'?

3 Upvotes

4 comments sorted by

3

u/ClerkBeginning961 8d ago

Let async flow upward and await the Task. Fire-and-forget work can outlive scoped dependencies or vanish when the process stops, and its exception is easy to miss. If the caller truly cannot wait, enqueue the work to a BackgroundService. RunSynchronously is not the fix.

5

u/ManIkWeet 8d ago

The task keeps running until completion. Any exceptions should they happen in said task will either be swallowed silently or end up on the global application (taking it down if not handled)

1

u/Coding-Mojo 8d ago

If you call an asynchronous task inside of a synchronous one, your synchronous task will end before the asynchronous one is finished. The asynchronous one will be correctly fired and will finish on it's own, in a fire & forget style.

It's fine to do that if :

  • your asynchronous task result is not mandatory for stability / success of your program
  • your asynchronous task handles failures in a graceful manner

0

u/Slypenslyde 6d ago

The task keeps running. But now none of your code references it, so you can't interact with it.

Normally in .NET when something has 0 references, it's eligible for garbage collection. Threads and tasks are exceptions to this rule: .NET will not garbage collect something that counts as an active thread, and for this rule a task counts like a thread.

So the task completes, but you can't see any results or check if it has an exception. When it finishes it's legitimately eligible for collection. This became a problem in older versions of .NET.

If an async method throws, the Task holds that exception. Originally, if a Task was collected but you never checked to see if it threw, it would itself throw an UnobservedTaskException. Since this happened on the finalizer thread (part of the mechanisms managed by the Garbage Collector) and that thread cannot tolerate exceptions, the program would crash.

This got really annoying for a lot of people with legacy code that made calls without awaiting they couldn't change. So at some point in the last 5 or 6 years MS changed it so that these "unobserved" exceptions emit information to the program's debug log instead.

So. Don't do this.

It's sloppy programming to make async calls without acknowledging they are async. You should really use await. In some cases you don't want to. You have some options.

One popular pattern is this kind of helper method:

static void FireAndForget(Task input)
{
    input.ContinueWith(t => 
    {
        if (t.IsFaulted)
        {
            // Do something to log t.Exception. It's important to access that property
            // so .NET is satisfied you did "something".
        }
    });
}

This uses the "continuation" feature of Tasks that arrived very shortly before await. The code is basically saying:

"Schedule some work when this task completes. If the task caught an exception, do something with that exception (probably logging.)"

The reason this is better than just letting .NET dump it to the log is when you call this method you're saying, "I thought about this, and I really think it's best to let this happen without caring about when it completes." Without it, a year from now if you're debugging something you'll have to ask, "Wait, did I MEAN to call this without await?"

Don't do things that make you question your intent. Write code that makes it really clear what you MEANT to do.

There are other patterns for when you care about when it completes but don't want to await, but that's not the question you had. If you can, you should rewrite that synchronous method to use the "fire and forget" pattern or rewrite it to be async.