Использование async await в Unity
Кто-то может сказать как такую конструкцию заставить работать? На данный момент возвращает UnityExtension
UnityException: get_canAccess 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.
private async void Awake()
{
Mesh protoMesh = gameObject.GetComponent<MeshFilter>().mesh;
SelfMesh = await InitMeshAsync(protoMesh);
}
protected async Task<CustomMesh> InitMeshAsync(Mesh protoMesh)
{
return await Task.Run(() => new CustomMesh(protoMesh.vertices, protoMesh.triangles));
}
Ответы (2 шт):
В Unity многие вещи можно делать только в главном треде. Одна из таких вещей это создание Mesh и его наследников (подозреваю, что CustomMesh наследует Mesh). А Task.Run выполняет задачу в другом треде. Поэтому надо писать так:
private void Awake()
{
Mesh protoMesh = gameObject.GetComponent<MeshFilter>().mesh;
SelfMesh = new CustomMesh(protoMesh.vertices,protoMesh.triangles);
}
P.S. бывают случаи при создании мешей, когда расчет позиций вертексов и треугольников занимает много времени и хочется вынести это в отдельный тред (при процедурной генерации например). И это можно делать при помощи Task, но опять же вызывать new Mesh() можно только в основном треде.
Для этого необходимо передать TaskScheduler, созданный на основе главного потока и использовать Task.Factory.StartNew() для получения элементов Mesh полагая, что new CustomMesh() не использует вызовы элементов Unity:
private async void Awake()
{
Mesh protoMesh = gameObject.GetComponent<MeshFilter>().mesh;
var mainTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
SelfMesh = await Task.Run(() => InitMeshAsync(protoMesh, mainTaskScheduler));
}
protected async Task<CustomMesh> InitMeshAsync(Mesh protoMesh, TaskScheduler scheduler)
{
Vector3[] vertices = null;
int[] triangles = null;
await Task.Factory.StartNew(() =>
{
vertices = protoMesh.vertices;
triangles = protoMesh.triangles;
}, CancellationToken.None, TaskCreationOptions.None, scheduler);
return new CustomMesh(vertices, triangles);
}
Можно упростить код и получить переменные vertices и triangles сразу в Awake(), а потом передать в InitMeshAsync(), чтобы не создавать лишний await, если это допустимо.
Если же new CustomMesh() использует вызовы элементов Unity, то использование асинхронности в данном случае не имеет смысла, поскольку всё придётся выполнять в главном потоке.
В случае доступа не из главного потока все вызовы к элементам Unity необходимо делать через такие функции (например, AddComponent<T>(), new Mesh()). В простых случаях следующего набора функций должно хватить:
public static Task RunAsyncWithScheduler(Action action, TaskScheduler scheduler) =>
Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, scheduler);
public static Task<T> RunAsyncWithScheduler<T>(Func<T> func, TaskScheduler scheduler) =>
Task.Factory.StartNew(func, CancellationToken.None, TaskCreationOptions.None, scheduler);
Неочевидным, как по мне, элементами Unity являются UnityEngine.Random и свойства (например, tranform). Так же я нашёл некоторые исключения из правил: new Gradient(), Mathf.