- Home /
Unity freezes when adding Task to List
I have the follwing code :
List<Task<Response>> tasks = new List<Task<Response>>();
foreach (string url in urls)
{
Task<Response> task = AsyncRequest(url);
tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
Where Response is a simple class with 2 members :
public class Response {
public bool isError;
public string content;
}
And the task is the following:
public async Task<Response> AsyncRequest(string url) {
Task<HttpResponseMessage> getTask = httpClient.GetAsync(url);
HttpResponseMessage message;
try
{
message = await getTask;
}
catch (Exception e)
{
Debug.LogFormat("GET request error for URL {0}: {1}", url, e);
return null;
}
Response response = new Response
{
isError = !message.IsSuccessStatusCode,
content = await message.Content.ReadAsStringAsync()
};
return response;
}
When the code is executed, Unity freezes on the line tasks.Add(task)
.
In my original code, the task was stored with a key in a Dictionary<string, Task<Response>>
, and it ended up the same way with a freeze of Unity.
If I create a list of tasks without result like List<Task> tasks
, the freezing behaviour doesn't happen anymore when adding elements to the list but when the line Task.WaitAll(tasks)
is reached.
Finally, I found out that using an array Task<Response>[] tasks
does the same thing as above and freezes at Task.WaitAll(tasks)
too.
My question is why does Unity crashes here ? What are the option I have to solve this issue ?
You could use Task.WaitAll(tasks, 1000);
to wait all tasks for a timeframe of 1000 milliseconds before continuing on. If it still freezes when this happens, errors may be occuring in the tasks, I guess, causing it to freeze. Try putting it into a try-catch statement and catching the exceptions, if there are any.
Right, one of the reasons I don't really like the async / Task mechanism. Depending on the active and used scheduler the behaviour could vary drastically. Unity usually uses a syncronous scheduler similar to the one that drives the coroutines. Therefore the async functions do not run on a seperate thread but are scheduled on the main thread. Depending on what the tasks look like, if they can not "yield" / finish properly you could trap yourself in a deadlock. Without knowing what those async tasks actually do it's hard to tell what's exactly happening. Also note that "Task.WaitAll" is also a task that should be awaited. Since we don't see the encosing / calling code it's even harder to follow the flow of control. Custom schedulers and promises / continuations makes it quite hard to reason about the exact behaviour, especially in more complex scenarios.
That's why I'm still a fan of coroutines as they are 100% deter$$anonymous$$istic once you understand how they work. You can't get trapped in a blocking infinite loop unless you literally created one because you did not yield. Async / await is much more obscure.
Don't get me wrong async / await is a much nicer and much more flexible syntax / concept and certainly is the future. Though I simply don't like to use code I do not fully understand or can not follow or reason about. You know, hope ain't a tactic ^^.