In asynchronous programming we’re constantly dealing with callback functions. Maybe you have to call some function in a third party library that takes a callback function. Regardless, Unity programmers often want to use coroutines for their asynchronous tasks. Today’s article show you how you can use callback-based code from your coroutines, all while being simple and easy to use. Read on to learn how!

Say you’ve got a function that downloads some text from a URL. The function requires a callback which is called when the download is complete. Its signature looks like this:

public static class UrlDownloader
{
	public static void DownloadText(string url, Action<string> callback);
}

The brute-force way of adapting this code into a coroutine might look something like this:

IEnumerator MyAsyncFunction()
{
	// ... other async work
 
	var done = false;
	var result = default(string);
	UrlDownloader.DownloadText("http://google.com", r => { result = r; done = true; });
	while (done == false)
	{
		yield return result;
	}
	Debug.Log("downloaded: " + result);
 
	// ... other async work
}

Using the callback-based function means that we have to add two temporary variables: done and result. We have to introduce a loop with a yield return in it. Finally, we have a lambda that controls how the loop ends and the result is saved. That’s a whole lot of extra work just to use the callback function. It’s not straightforward code, it’s not easy to read, and bugs are likely.

Let’s see if we can do better by using a CustomYieldInstruction to wrap the callback. Here’s one you can copy into your projects:

using UnityEngine;
 
// Custom yield instruction to wait until a callback is called
// by Jackson Dunstan, http://JacksonDunstan.com/articles/3678
// License: MIT
public class WaitForCallback<TResult> : CustomYieldInstruction
{
	/// <summary>
	/// If the callback has been called
	/// </summary>
	private bool done;
 
	/// <summary>
	/// Immediately calls the given Action and passes it another Action. Call that Action with the
	/// result of the callback function. Doing so will cause <see cref="keepWaiting"/> to be set to
	/// false and <see cref="Result"/> to be set to the value passed to the Action.
	/// </summary>
	/// <param name="callCallback">Action that calls the callback function. Pass the result to
	/// the Action passed to it to stop waiting.</param>
	public WaitForCallback(Action<Action<TResult>> callCallback)
	{
		callCallback(r => { Result = r; done = true; });
	}
 
	/// <summary>
	/// If the callback is still ongoing. This is set to false when the Action passed to the
	/// Action passed to the constructor is called.
	/// </summary>
	override public bool keepWaiting { get { return done == false; } }
 
	/// <summary>
	/// Result of the callback
	/// </summary>
	public TResult Result { get; private set; }
}

Now you can replace the complicated code in your coroutine so it looks like this:

IEnumerator MyAsyncFunction()
{
	var download = new WaitForCallback<string>(
		done => UrlDownloader.DownloadText("http://google.com", text => done(text))
	);
	yield return download;
	Debug.Log("downloaded: " + download.Result);
}

The new code has no temporary variables or loops to clutter up the coroutine. Instead, it creates an instance of WaitForCallback and passes it a lambda which is immediately called. That lambda just calls DownloadText, but with a callback function that calls the done callback that WaitForCallback passed. It’s a bit tough to explain in English, but the code is very simple.

Next just yield return the WaitForCallback instance and the coroutine will be suspended until DownloadText is done. Afterward you can get the downloaded text from WaitForCallback.Result and do what you like with it.

Feel free to share any strategies you use to deal with callback function in the comments!