UniTask
UniTask copied to clipboard
Awaiting and then reading result causes an exception
This code throws in GetResult:
protected async virtual UniTask Init() {
foreach (var prop in GetProperties()) {
if (prop.GetValue(this) == null) {
var methodInfo = prop.PropertyType.GetMethod("Create", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
var task = methodInfo.Invoke(null, new object[]{}); // returns UniTask<something>
var baseTask = task.GetType().GetMethod("AsUniTask").Invoke(task, new object[]{}); // returns UniTask
await (UniTask)baseTask;
var awaiter = task.GetType().GetMethod("GetAwaiter").Invoke(task, new object[]{}); // returns UniTask<something>.Awaiter
// After I updated some versions, this now throws; but if you look at the above, the value from the Create() method has
// never actually been returned by any method invocation.
var result = awaiter.GetType().GetMethod("GetResult").Invoke(awaiter, new object[]{}); // returns 'something'
prop.SetValue(this, result);
}
}
}
Is this expected? I would think the resulting value sticks around until all refs to the UniTask itself are removed. I could try using Preserve()
to 'await multiple', but I'm not actually awaiting more than once.
Yes, that is expected, as once a UniTask instance is awaited, it is no longer valid, unless it's preserved. Since you're using reflection to handle async invokes here, I suggest refactoring your code to something more like this:
public class MyType
{
protected async virtual UniTask Init()
{
var openGenericSetPropertyMethod = typeof(MyType)
.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)
.First(m => m.Name == nameof(SetPropertyAsync));
foreach (var prop in GetProperties())
{
if (prop.GetValue(this) == null)
{
var methodInfo = prop.PropertyType.GetMethod("Create", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy); // returns UniTask<T>
var resultType = methodInfo.ReturnType.GetGenericArguments()[0]; // typeof(T)
var asyncMethod = openGenericSetPropertyMethod.MakeGenericMethod(new Type[] { resultType });
await (UniTask) asyncMethod.Invoke(this, new object[] { prop, methodInfo });
}
}
}
private async UniTask SetPropertyAsync<T>(PropertyInfo propertyInfo, MethodInfo methodInfo)
{
T result = await (UniTask<T>) methodInfo.Invoke(null, Array.Empty<object>());
propertyInfo.SetValue(this, result);
}
}
[Edit] This code might not work in IL2CPP prior to Unity 2022.1 if the T
is a struct or primitive. I think for that you will have to use the Preserve()
method.
@timcassell Thanks for that! That's an interesting way to get around the issue - I was hoping there was some way to get a return value from an await by just telling the runtime the specific type it should expect, but I couldn't find any support for doing that. I ended up just using Preserve, but never like running steps that conceptually shouldn't be required. T in this case should always be a class, if I recall correctly, though I am using 2021 LTS.
This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 7 days.