A task that awaiting a faulted task has status == WaitingForActivation, but Faulted expected

Code to reproduce:

using UnityEngine;
using UnityEditor;
using System.Threading.Tasks;
using System.Threading;
using System;

public static class _Test_
{
    [MenuItem("Tools/Run Test")]
    public static void Test()
    {
        var task = StartProcess();
        Thread.Sleep(1000);
        Debug.Log(task.Status);     // WaitingForActivation
        Debug.Assert(task.Status != TaskStatus.WaitingForActivation);       // Assertion failed
    }
    
    static async Task StartProcess()
    {
        await Task1();
    }
    
    static async Task Task1()
    {
        await Task.Delay(200);
        throw new Exception("Task1 error");
    }
}
    

The same code run with dotnet 9.0.101 gives a different result:


using System.Threading.Tasks;
using System.Threading;
using System.Diagnostics;

public class Program
{
    public static void Main(string[] args)
    {
        var task = StartProcess();
        Thread.Sleep(500);
        Console.WriteLine(task.Status);     // Faulted
        Debug.Assert(task.Status != TaskStatus.WaitingForActivation);
    }
    
    static async Task StartProcess()
    {
        await Task1();
    }
    
    static async Task Task1()
    {
        await Task.Delay(200);
        throw new Exception("Task1 error");
    }
}

Unity version is 6000.0.26f1

Why would you use Thread.Sleep? Unity’s synchronization context executes tasks on the main thread, so if you suspend the whole main thread, it’s not that surprising that no queued tasks get executed.

Change Thread.Sleep to Awaitable.WaitForSecondsAsync and you’ll get the results you’re expecting.

Its a minimal reproduction of the issue. The actual thing I want is task.Wait(), which blocks the main thread as well.

What I want to achieve is to start a task at the end of PostLateUpdate, let it do some computation, and wait it at the beginning of next frame’s PreUpdate so that I can merge results to Unity world. The blocking behaviour is intened.

I thought the task should run automatically like what Task.Run do when I call StartProcess(), while it is not, by design?

Like was mentioned, Unity runs tasks on the main-thread by default. If you immediately block the main thread after starting a task, I imagine it never gets updated by Unity’s synchronisation context.

Mind you, I’m not sure of the timing that which async code gets updated out-side of play mode, so I don’t think this is an effective repro anyway as the behaviour might be completely different to runtime.

If you’re trying to do heavy calculations on a thread, you should be using Jobs anyway.

1 Like

That sounds like a recipe for deadlocks. You could easily end up making the main thread wait for a task to complete, which never will, since the main thread will never continue to execute the next step in the task.

The job system is probably the way to go instead.

1 Like

Also worth noting that Task.Delay and likely Thread.Sleep too are not functional on the Web platform, they will just freeze indefinitely.

Avoid using Thread class in Unity in general. As the others pointed out, Unity has the Job system which significantly outperforms C# Threads if used properly.