When you build HoloLens apps using Unity and Visual Studio you will come across some difficulties when implementing web calls. Especially when web calls takes a long time. That will cause holographic objects like your gaze cursor to freeze. The problem is that Unity is not thread safe. That means that Unity does not really allow you to use any asynchronous calls.
Unity equivalent
Unity provides a method StartCoroutine(IEnumerator routine) on the MonoBehaviour class. This method returns a Coroutine class which is used only to reference the coroutine itself. It allows you to execute behaviour over multiple frames and can be seen as some kind of asynchronous call. It has no overhead for the app whatsoever which makes it handy to use. But keep in mind that running time consuming code within the method specified by the StartCoroutine will freeze your frames causing the hold your scene containing the holographic objects.
Truly asynchronous calls
If you want truly asynchronous calls you will need to run your code in a separate thread. Threading will enable your app to accomplish work asynchronously in parallel threads. We want to use Windows.System.Threading.ThreadPool.RunAsync(WorkItemHandler) to execute asynchronous web calls.
HoloLens applications are just Microsoft Windows Store Apps. An apps support functionality like the ThreadPool class. But the problem is that it is not accepted by Unity. Unity is based on .NET 3.5 framework and does not support multi-threading. That means as soon as you start rebuilding your project from Unity you will have build errors. And that means the Visual Studio project can not be build or rebuild.
Therefor our class will be setup in such a way that by using a compiler directive the asynchronous code is not compiled during the build via Unity. After Unity has created the Visual Studio solution and projects you will need to add the compiler directive. That compiler directive causes the asynchronous code to be included in the build for HoloLens.
Implementing the skeleton of the API class
The skeleton of the class is based on a singleton principle and contains some includes for our asynchronous code. We will be using a (made-up and original) compiler directive PLATFORM_HOLOLENS to exclude/include the code in builds. By adding the compiler directive to the HoloLens project as a conditional compiler symbol the code is included in the build.
using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Net; using System.Text; using UnityEngine; #if (PLATFORM_HOLOLENS) using Windows.Foundation; #endif public class API { public delegate void OnGetDataCompleted(string id, string json); private static API instance = null; private API() { } public static API Instance { get { if (instance == null) { instance = new API(); } return instance; } } }
Implementing the POST method
We going to implement the first method for an asynchronous POST action. A POST action allows us to send data to a specific URL. In most cases you need to send additional data along with the call. In our example we use a JSON string converted to a byte array. The calling method will use something like jsonBytes = System.Text.Encoding.UTF8.GetBytes(json);
public void PostDataAsync(string url, byte[] jsonBytes) { #if (PLATFORM_HOLOLENS) IAsyncAction asyncAction = Windows.System.Threading.ThreadPool.RunAsync( async (workItem) => { WebRequest webRequest = WebRequest.Create(url); webRequest.Method = "POST"; webRequest.Headers["Content-Type"] = "application/json"; Stream stream = await webRequest.GetRequestStreamAsync(); stream.Write(jsonBytes, 0, jsonBytes.Length); WebResponse response = await webRequest.GetResponseAsync(); } ); asyncAction.Completed = new AsyncActionCompletedHandler(PostDataAsyncCompleted); #endif } #if (PLATFORM_HOLOLENS) private void PostDataAsyncCompleted(IAsyncAction asyncInfo, AsyncStatus asyncStatus) { } #endif
The method uses an asynchronous method inside the ThreadPool.RunAsync() method. That method returns an interface to a work item. Inside the method a WebRequest is build and used to write through a Stream object the JSON data into the request. Finally the web call is made through GetResponseAsync(). Keep in mind that the PostDataAsyncCompleted method is called when the GetResponseAsync() method has finished. Meaning that any data set after the GetResponseAsync() method is not seen by that event method.
Implementing the GET method
Next we are going to implement the second method for an asynchronous GET action. A GET action allows us to get data returned by calling a specific URL. Normally you use the URL to provide additional data (e.g. https://contoso.com/api/content/5) to a REST API call. So in most cases you do need to add additional data. In this example we left it out. But if needed it has the same implementation as the POST method.
public delegate void OnGetDataCompleted(string id, string json); public void GetDataAsync(string url, string id, OnGetDataCompleted handler) { #if (PLATFORM_HOLOLENS) IAsyncAction asyncAction = Windows.System.Threading.ThreadPool.RunAsync( async (workItem) => { try { WebRequest webRequest = WebRequest.Create(url); webRequest.Method = "GET"; webRequest.Headers["Content-Type"] = "application/json"; WebResponse response = await webRequest.GetResponseAsync(); Stream result = response.GetResponseStream(); StreamReader reader = new StreamReader(result); string json = reader.ReadToEnd(); handler(id, json); } catch (Exception) { // handle errors } } ); asyncAction.Completed = new AsyncActionCompletedHandler(GetDataAsyncCompleted); #endif } #if (PLATFORM_HOLOLENS) private void GetDataAsyncCompleted(IAsyncAction asyncInfo, AsyncStatus asyncStatus) { } #endif
Again the method uses an asynchronous method inside the ThreadPool.RunAsync() method. That method returns an interface to a work item. Inside the method a WebRequest is build and used to call the GetResponseAsync() method. A stream object is used to retrieve the result data from the web call. As you can see in the code we have implemented the Completed event with the GetDataAsyncCompleted() method. But be aware! This method is called as soon as the GetResponseAsync() method is finished. So handling the returned data in the completed method is not possible. Therefor we use a delegate. The method based on the delegate is given through the parameters and is used to send a completed message containing the id and data. So the calling party on the GET method can give it’s own method based on the delegate to do additional actions after the response has completed.
Conclusion
It is possible to use asynchronous calls using HoloLens development with Unity and Visual Studio. The problem arises when you use stuff which is part of .NET framework above version 3.5. By using a compiler directive you can ensure that changes made in Unity which causes to do a build from Unity will still work. Keep in mind that rebuilding your Visual Studio solution will reset the conditional compiler symbols. That means that after a rebuild with Unity you need to add PLATFORM_HOLOLENS again at the project Assembly-CSharp project in your solution. The downside is that you can’t test or run your asynchronous code in Unity. It is not part of the build due to the compiler directive and the non-ability of Unity. If you want to do testing from Unity I would recommend to add an additional non asynchronous method part using for example the WWW class or the UnityWebRequest class to perform the POST and GET method.
I will be posting shortly another blog post which uses this class to update text around a holographic object based on an Azure Cloud Service.
Good post, thanks. Consider rather than PLATFORM_HOLOLENS try the built in UNITY_UWP (which is geared for this out of the box)
Specifically while in Unity if(!UNITY_EDITOR && UNITY_UWP) or NETFX_CORE (the latter is only enable when doing your final compile in VS while UNITY_UWP is set once your build target is set, hence the check also for !UNITY_EDITOR to make sure you aren’t running in the editor when compiling which must mean you are building)
The problem is with UNITY_EDITOR that it does not work. I tried that first before i started using an own compiler directive. UNITY_EDITOR is only to determine if you are in the editor, while i just wanted nothing of the code to be compiled when working in Unity regardless which mode i am in.
Really nice article. Would be perfect, if you add a usage example.
So how do I use it?
I assume I create an API Object?
Something like this?
API apiObject = new API();
apiObject.PostDataAsync(“http://stackoverflow”,{});
Greets Senador
Hi…
Use API api = new API.Instance;
And indeed than call the functions
Hi,
I am doing get request in orderto get some data. Using Your code I get the following error:
SetActive can only be called from the main thread.
Constructors and field initializers will be executed from the loading thread when loading a scene.
Don’t use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
Could You please help me to solve it?
My BR
Marta
I’m not sure what you are doing exactly. When do you call SetActive? Do you have some coding example?
thanks for creating such good article, easy to understand and use..
I know basic C# and Unity, where to learn advanced C# like networking, Threads, Events
can you refer me any book or tutorials..
thanks,
vivek,
Indie game developer
[…] Before we start explaining how we integrate a solution which connect to the Cloud, we need to understand the process of developing an application for HoloLens. Normally one use Unity to build your scene. The scene contains hologram objects attached with code running in the holograms context. As soon as you have build your scenery, it allows you to create an Visual Studio solution with the holograms and other stuff as assets. That Visual Studio solution is used to create the UWP application and deploys it to the HoloLens. As soon as something is changing on a Unity level (think of changes in your scene or holograms) we need to recreate the build of the Visual Studio solution. Unity compiles against its own SDK libraries. If you changed code in Visual Studio in the mean time like adding asynchronous code, it will not compile. You need to add precompiler directives and move stuff to separate libraries which you include from your project. An example of the use of precompiler directives can be found here. […]
[…] Before we start explaining how we integrate a solution which connect to the Cloud, we need to understand the process of developing an application for HoloLens. Normally one use Unity to build your scene. The scene contains hologram objects attached with code running in the holograms context. As soon as you have build your scenery, it allows you to create an Visual Studio solution with the holograms and other stuff as assets. That Visual Studio solution is used to create the UWP application and deploys it to the HoloLens. As soon as something is changing on a Unity level (think of changes in your scene or holograms) we need to recreate the build of the Visual Studio solution. Unity compiles against its own SDK libraries. If you changed code in Visual Studio in the mean time like adding asynchronous code, it will not compile. You need to add precompiler directives and move stuff to separate libraries which you include from your project. An example of the use of precompiler directives can be found here. […]
Hi Alexander,
Can you try the following package? https://assetstore.unity.com/packages/tools/network/rest-client-for-unity-102501
Let me know what you think
Thanks in advance, Nicholls
I do not understand how to implement this code please advise