Awaitable equivalent of Task.CompletedTask

Is there a equivalent of Task.CompletedTask for Awaitable class?
I have a scenario where I would like to pass Func<Awaitable> around but some callbacks don’t have to perform async actions.

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

class Foo
{
    public async void Main()
    {
        Func<Awaitable> func = () =>
        {
            Debug.Log("I don't need to do any async code in this callback");
            // How can I exit without awaiting?
            // This is not compile
            return Awaitable.Completed;
        };
        await func();
        
        // System.Threading.Tasks.Task does not have this problem
        Func<Task> func2 = () =>
        {
            Debug.Log("I don't need to do any async code in this callback");
            // This is fine
            return Task.CompletedTask;
        };
        await func();
    }
}```

There isn’t unfortunately… and since the class uses object pooling internally, I’m not sure if there’s any easy and safe way to create your own either :thinking:

Your best option would probably be something using AwaitableCompletionSource similar to this: AwaitableUtility.FromResult

1 Like

Currently using this as an alternative to Task.CompletedTask in a static AwaitableUtilities class. Posting a response since this is one of the top search results that technically doesn’t have a response yet.

   public static Awaitable Completed() => CompletedAwaitable.Instance;

        private static class CompletedAwaitable
        {
            private static readonly AwaitableCompletionSource s_completionSource = new();

            static CompletedAwaitable()
            {
                s_completionSource.SetResult();
            }

            public static Awaitable Instance => s_completionSource.Awaitable;
        }

I appreciate you taking the time to post a solution here - however, that is not a safe implementation! :warning:

Awaitable uses object pooling underneath the hood. When control returns back to a method that uses the await keyword with an Awaitable instance, the Awaitable object gets returned back into the object pool for later reuse.

This is why one should never cache an Awaitable object in a member variable, nor await one more than once! If you reuse an Awaitable instance, it can be hooked up to completely different operation every time you await it, potentially leading to weird bugs that can be difficult to track down.

So when you use AwaitableCompletionSource, makes sure to always either:

  1. Create a new instance from scratch every time.
  2. Call Reset on instances you reuse every time after you’ve acquired an Awaitable from it, and before calling SetResult on it again.

Here is a solution that doesn’t generate garbage and works reliably:

public static class AwaitableUtility
{
	static readonly AwaitableCompletionSource completionSource = new();
	
	public static Awaitable CompletedAwaitable
	{
		get
		{
			completionSource.SetResult();
			var awaitable = completionSource.Awaitable;
			completionSource.Reset();
			return awaitable;
		}
	}
}
4 Likes

I was not aware, thank you for the improved code.

1 Like