How to Easily Use Callback Functions in Coroutines
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!
#1 by Vince on November 28th, 2016 ·
I’m not a unity user, but read your blog to improve my knowledge of csharp. Does unity not allow async await?
#2 by jackson on November 28th, 2016 ·
Unity is unfortunately stuck on an old version of Mono that’s somewhere between .NET 2.0 and 3.5. As such it doesn’t provide the
async
andawait
features that were introduced in later versions of .NET. They’re probably a much better solution than coroutines and callbacks though, so you should probably use them if you have access to them.Feel free to let me know if you have any ideas for articles, especially as a non-Unity programmer. Many of the articles on this site are applicable to C# programming outside of Unity, so it’d be nice to hear what you’re interested in reading.
#3 by Steve on September 23rd, 2017 ·
This is no longer the case with newer versions of Unity as described here: http://www.stevevermeulen.com/index.php/2017/09/23/using-async-await-in-unity3d-2017/
#4 by jackson on September 23rd, 2017 ·
That’s true: as of Unity 2017.1 we have access to experimental support for C# 6 and it’s
async
andawait
keywords. Thanks for providing that link. It’s a great introduction to these newer features and how they fit into the existing features (e.g. coroutines) that Unity provides.What a great start to the blog! I’m subscribed on RSS now and looking forward to more articles like this. :-D
#5 by Steve on October 1st, 2017 ·
Thanks :D Glad you liked it
I spent a bunch of time digging into it and couldn’t find a lot of other resources online so figured it would be worth getting my thoughts down somewhere