Async methods vs Threads

There are many new features introduced in C# 4,5 and 6 I would really like to start implementing in my code. Recently came across this ‘hack’ which apparently allows support of most of these features. https://bitbucket.org/alexzzzz/unity-c-5.0-and-6.0-integration/src

Along with AsyncBridge even Async methods are supported. https://www.simple-talk.com/blogs/asyncbridge-write-async-code-for-net-3-5/

As an async-thread novice my question is, are Async methods in general preferable to threads and can threads be replaced using Async methods in most cases?

preferable is debatable…

Async/Await is a language syntax built around the Task library.

Tasks are similar to threads, in that they often operate on threads pulled from the ThreadPool.

Threads could be considered more lower level, giving you more discrete control with how a thread is dealt with, where as a Task is more just a set it and forget type setup. You allow the .Net framework to manage all the monkey business for you, and rather you just care about getting some work done asynchronously.

When you use what really depends on what you’re doing. So I can’t really say they’re more preferable… Unity is a thread sensitive environment. You honestly should be avoiding threads for the most part unless you’re designing some structural aspect in the game.

But really… what the whole async/await system allows you to do is syntactically allow 1 thread to wait on other threads. I want to block on thread A until thread B has finished downloading a file and unpacked the data to than be used back on thread A.

Here’s the thing… this is NO good for the unity main thread. You don’t want thread blocking… ever… it needs to continue on and redraw the screen for us (hence why our coroutines instead wait frames with ‘yield return null’ until some ‘finished’ flag copletes or the sort). So really, you’re going to only get use out of it when you spin up a asynchronous task that then spins up yet another task, and you want to wait on that task. Otherwise, it just gives you a slightly shorter line of code to start up a async task, while forcing the task callback to only ever be async.

Cause the big thing is that a lot of the unity framework doesn’t exactly integrate well with the async/await framework. So the things like asynchronous event handlers and the sort… not exactly available. And even if they were… well, there’s no way to get back to the unity thread which isn’t thread safe to these tasks. You’re just changing your road blocks from orange to red. They’re still roadblocks.

5 Likes

Really depends exactly what you are doing. Often in games the explicit control of managing your own threads is useful. But if you are doing a bunch of small stuff, handing it off to tasks is fine.

Integrating threading into Unity is a bit of a pain, but if you need it, it can be done.

3 Likes

In the end, I stand by designs like ThreadNinja for MOST scenarios.

I myself use my own version that is similar to ThreadNinja. It’s a very unity friendly approach for simple asynchronous tasks. It gives you that thread hopping between unity main thread and your async tasks in a syntax that is familiar to unity’s coroutines.

Here you can see my tools for it, I call it ‘RadicalTask’ to go with my RadicalCoroutine.

But if it’s not game logic specific… I often just go with classic threads because I’m doing more low level stuff anyways.

You’ve described the synchronous approach, where you call something and freeze until that something completes. The async stuff in Unity Runtime Upgrade Preview and my async stuff for the current versions of Unity work different, just like in WinForms and WPF applications.

For example, you have a button on the screen and a corresponding OnClick handler in the code:

    public async void OnClick()
    {
        button.interactable = false; // runs on the main thread right away

        await Task.Factory.StartNew(DoStuff);

        button.interactable = true; // will run on the main thread in some distant future
    }

By the way, you can’t use coroutines as direct event handlers, but async methods fit here perfectly.

The user presses the button and OnClick() gets called. The method deactivates the button, launches the task, that is supposed to be doing some CPU- or IO-heavy stuff, and immediately returns. No freezing, no blocking the main thread. The button is inactive, the heavy stuff is running somewhere (in the thread pool by default), the main thread is also running and doing what it usually does.

In a second or in an hour or after ten millions of frames, when the heavy calculation finally completes, the continuation (the rest of the method, everything after the await line) will to be executed. This will happen on the main thread in a way not much different from how Unity runs different sections of coroutines that spread their work over multiple frames.

Basically, the coroutines and async/await look and feel very similar. Coroutines in Unity are a poor-man’s way to do asynchronous calls, invented at the times when the feature wasn’t directly supported by the language.

Async/await requires a bit less boilerplate code, async methods can return results, and await is easily extensible. You can await any thing, if that thing has a corresponding awaiter. For example, you can write an awaiter for Func to await some arbitrary condition to become true in one line. I wrote this example yesterday just for fun. There is room for improvement, but as a proof of concept works fine.

using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

public static class Until
{
    public static ConditionAwaiter GetAwaiter(this Func<bool> condition) => new ConditionAwaiter(condition);
    public static ConditionAwaiter IsTrue(Func<bool> condition) => new ConditionAwaiter(condition);

    private static async void UsageExamples()
    {
        await (Func<bool>)(() => DateTime.Now.Second == 0);

        await new Func<bool>(() => DateTime.Now.Second == 0);

        await Until.IsTrue(() => DateTime.Now.Second == 0);
    }
}

public class ConditionAwaiter : INotifyCompletion
{
    private readonly Func<bool> condition;
    private readonly SynchronizationContext context;

    public ConditionAwaiter(Func<bool> condition)
    {
        this.condition = condition;
        context = SynchronizationContext.Current;
    }

    public ConditionAwaiter GetAwaiter() => this;

    public void GetResult() { }

    public bool IsCompleted => condition();

    public void OnCompleted(Action action)
    {
        Task.Factory.StartNew(() =>
                              {
                                  while (condition() == false)
                                  {
                                      Thread.Sleep(16);
                                  }

                                  if (context != null)
                                  {
                                      context.Post(state => action(), null);
                                  }
                                  else
                                  {
                                      action();
                                  }
                              }, TaskCreationOptions.LongRunning);
    }
}
3 Likes

Nah, that’s roughly what I was expecting to act like.

The only thing I wasn’t, is that it returned to the unity main thread so easily. I’m assuming you added something to facilitate that? Hence the SynchronizationContext.

1 Like

Yep, I did. The recent Unity Mono Runtime Upgrade Preview has a built-in synchronization context.

But since we already have coroutines, what does this gain me? If anything it looks like I’m loosing control of when I get the callback on the main thread. Which is something I can easily control with a coroutine.

Sure I loose a few lines of boilerplate code. But that’s just some nice syntactic sugar. If I was after sugar I’d switch to UnityScript. :wink:

Relatively soon you will have in Unity both coroutines and async/await, able to do the a similar job in a similar way. The one is designed to be configurable, extensible and is built-in in the language, another one is a hack.

2 Likes
  • Use async to avoid blocking the user interface while waiting for some slow process (expl: getting data over the network), and to not waste threads (async methods are meant to run on the same thread).
  • Use threads for CPU intensive process (number crunching).

As already mentioned here, Unity is not thread safe. So, once async/await would be supported, you would be more likely to use it more often than threads.

Just to confirm, in order to get async await functionality in current 5.5.1 public release, we need to implement BOTH CSharpVnext & AsyncBridge?

Another option, one that I use oftern, is promises. A more web approach but it’s so damn good I use it all the time. It can completely replace coroutines.

2 Likes