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);
}
}
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 yield
ing 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));
});