Search code examples
c#unity-game-enginecoroutinewebrequestunitywebrequest

Starting Coroutine in Main Thread


So I'm trying to execute a UnityWebrequest, which needs to start from a Coroutine. Now trying to Debug everything at some point I figured out calling the Coroutine from the Start function works (receiving the Debug.Log messages), but calling it from a normal function it will not work (I do not receive the debug messages within GetRequest).

Hence I think that for some reason the Coroutine is not running on the main-thread (as also no error appears).

Does anybody know how I can enforce a Coroutine to run on the main-thread or another solution to my issue?

Here you'll find the code where WebGet is being called from a class that is not in Monobehaviour, but the class containing WebGet & GetRequest is.

(Please ignore the request variable within WebGet this will be for a later stage.

 public UnityWebRequest WebGet(string url)
        {
            Debug.Log("This is the URL Get " + url);
            var request = UnityWebRequest.Get(url);

            Debug.Log("Request URL: " + request.url);

            StartCoroutine(GetRequest(url));

            Debug.Log("After Coroutine");

            return request;
        }

private IEnumerator GetRequest(string url)
        {
            var request = UnityWebRequest.Get(url);

            Debug.Log("This is the URL inside coroutine " + url);

            yield return request.SendWebRequest();

            if (request.isNetworkError)
            {
                Debug.Log("Error While Sending: " + request.error);
            }
            else
            {
                Debug.Log("Received: " + request.result);
            }

        }

Solution

  • Most of the Unity API can only be used on the Unity main-thread, StartCoroutine is one of those things.

    or another solution to my issue

    If you already are on a background thread you can also simply use UnityWebRequest.Get without any Coroutine and just wait using e.g.

    request.SendWebRequest(); 
    while(!request.isDone)
    { 
        // depends on your use case, but in general for the love of your CPU
        // you do want to wait a specific amount that is "ok" as a delay
        // you could of curse reduce this to approximately frame wise check by using e.g. 17 ms (=> checks 60 times per second)
        Thread.Sleep(50); // checks 20 times per second
    }
    
    if (request.isNetworkError)
    {
        Debug.Log("Error While Sending: " + request.error);
    }
    else
    {
        Debug.Log("Received: " + request.result);
    }
    

    Have in mind though that your are still on a background thread and also other Unity API calls might not be allowed at this point.


    If your method is not on a background thread in general note that StartCoroutine does NOT delay the method calling it (this would be exactly what we want to avoid by using a Coroutine) but rather immediately continues with the rest of your code.

    You can of course do something like

    var request = UnityWebRequest.Get(url);
    request.SendWebRequest();
    return request;
    

    without yielding it and outside of any Coroutine but then it is up to you to ensure that request.isDone is true before you continue accessing and using the results.


    How I can enforce a Coroutine to run on the main-thread?

    The alternative in order to force things happening on the main thread you can use a pattern often called Main thread dispatcher. A simplified version could look like e.g.

    public class MainThreadDispatcher : MonoBehaviour
    {
        #region Singleton pattern (optional)
        private static MainThreadDispatcher _instance;
        public static MainThreadDispatcher Instance => _instance;
    
        private void Awake()
        {
            if(_instance && _instance != this)
            {
                Destroy(gameObject);
                return;
            }
    
            _instance = this;
            DontDestroyOnLoad(gameObject);
        }
        #endregion Singleton
    
        #region Dispatcher
        // You use a thread-safe collection first-in first-out so you can pass on callbacks between the threads
        private readonly ConcurrentQueue<Action> mainThreadActions = new ConcurrentQueue<Action>();
        
        // This now can be called from any thread/task etc
        // => dispatched action will be executed in the next Unity Update call
        public void DoInMainThread(Action action);
        {
            mainThreadActions.Enqueue(action);
        }
        
        // In the Unity main thread Update call routine you work off whatever has been enqueued since the last frame
        private void Update()
        {
            while(mainThreadActions.TryDequeue(out var action))
            {
                action?.Invoke();
            }
        }
        #endregion Dispatcher
    }
    

    and then in your case you could use

    //MainThreadDispatcher.Instance.DoInMainThread(() =>
    yourMainThreadDispatcherReference.DoInMainThread(() =>
    {
        StartCoroutine(GetRequest(url));
    });