Quick answer: Calling .Result or .Wait() on a Task whose continuation needs the main thread deadlocks. Use await. For Unity 2023+, prefer Awaitable for main-thread async; reserve Task for non-Unity async APIs and add .ConfigureAwait(false) when you do not need to return to the main thread.

Here is how to fix Unity hanging when you mix Task-based async code with coroutines or editor scripts. You write var data = SomeAsync().Result; and the editor freezes. Or in Play mode, calling await in a MonoBehaviour seems to swallow the rest of the method. The cause is the synchronization context: Unity expects continuations on the main thread, but blocking on Tasks prevents the main thread from running them.

The Symptom

Editor freezes when an editor utility calls Task.Result. Or in Play mode, async methods stop after the first await with no apparent reason. Pressing Stop unfreezes the editor; logs show the await never returned.

What Causes This

Sync wait on async result. .Result blocks the calling thread (main thread). Inside the awaited task, when ready to continue, it tries to post the continuation back to the main thread — which is blocked waiting on .Result. Classic deadlock.

SynchronizationContext capture. By default, await captures the current context and resumes on it. If that context is the main thread and the main thread is busy, the continuation never runs.

Awaiting non-Unity Task in coroutine. Coroutines yield with IEnumerator; awaiting a Task does not interoperate cleanly. Mixing leads to confused timing.

The Fix

Step 1: Use await, not .Result.

// BAD: deadlocks if continuation needs main thread
var data = SomeAsync().Result;

// GOOD: cooperatively yields
var data = await SomeAsync();

Step 2: Use ConfigureAwait(false) for context-free continuations.

async Task<string> FetchAsync(string url)
{
    using var client = new HttpClient();
    var resp = await client.GetStringAsync(url).ConfigureAwait(false);
    return resp.ToUpper();
}

The continuation can run on a thread pool thread; Unity-specific code (Transform, GameObject) must not run there.

Step 3: Use Awaitable in Unity 2023+.

async Awaitable DelayedAction()
{
    await Awaitable.WaitForSecondsAsync(1f);
    transform.position = newPos;
}

Awaitable is Unity-aware and runs continuations on the main thread cooperatively.

Step 4: For editor scripts, never block on Tasks. Editor utilities that use Task should be written to be async themselves and run via async editor coroutines or progress-bar-driven loops:

[MenuItem("Tools/Fetch Data")]
static async void Fetch()
{
    try
    {
        var data = await FetchAsync();
        AssetDatabase.CreateAsset(data, "Assets/data.asset");
    }
    catch (System.Exception e) { Debug.LogError(e); }
}

Async void is acceptable in editor menu items where you cannot return Task.

Step 5: For coroutines that need to await a Task, use a bridge.

IEnumerator WaitForTask(Task t)
{
    while (!t.IsCompleted) yield return null;
    if (t.IsFaulted) throw t.Exception;
}

IEnumerator DoWork()
{
    var task = SomeAsync();
    yield return WaitForTask(task);
    Debug.Log(task.Result);   // safe now: task is done
}

The yield-loop polls completion without blocking, allowing the main thread to dispatch continuations.

Cancellation

Long-running async work should accept a CancellationToken. Cancel from OnDisable/OnDestroy to avoid awaits hanging on destroyed objects:

private CancellationTokenSource cts;

void Start()
{
    cts = new CancellationTokenSource();
    _ = RunAsync(cts.Token);
}

void OnDestroy() { cts?.Cancel(); cts?.Dispose(); }

“Never .Result in Unity. await everything. Awaitable for new code; ConfigureAwait(false) for off-Unity work.”

Related Issues

For InstantiateAsync, see InstantiateAsync Callback. For scene loading, see Async Scene Loading.

await, not .Result. Awaitable for Unity. ConfigureAwait(false) when leaving Unity. No deadlocks.