Comments (6)
I'm thinking AwaitWithProgress(float min, float max)
would be a better method name. Then the other ConfigureAwait
with cancelation token proposal #11 can change to WaitAsync
with more options like what TPL is doing dotnet/runtime#48842.
from protopromise.
Placing this here to remove it from the source until it's implemented...
Unfortunately, we can't do a simple if (typeof(TAwaiter) == typeof(PromiseAwaiter<T>))
, because we don't have access to the <T>
type. (This would work for PromiseAwaiter
without a generic type, though.)
internal interface IPromiseAwaiter
{
void AwaitOnCompleted(Internal.AsyncPromiseRef asyncPromiseRef);
}
PromiseAwaiters implement this interface. This can be used to subscribe to progress for the async Promise().
public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : INotifyCompletion
where TStateMachine : IAsyncStateMachine
{
if (typeof(TAwaiter).IsValueType && typeof(IPromiseAwaiter).IsAssignableFrom(typeof(TAwaiter)))
{
AwaitOverrider<TAwaiter>.awaitOptimizer.AwaitOnCompleted(ref awaiter, _promise);
}
else
{
awaiter.OnCompleted(_promise.GetContinuation(ref stateMachine));
}
}
In the builder, the IsValueType
and IsAssignableFrom
can theoretically be constant-optimized by the JIT/AOT compiler. IsValueType
I believe is already optimized this way in some runtimes/compilers, while IsAssignableFrom
I don't think is, but there is an open issue in .Net runtime to add that optimization. In any case, the IsValueType
helps it avoid that check when it's not necessary.
internal abstract class AwaitOverrider<T> where T : INotifyCompletion
{
internal static AwaitOverrider<T> awaitOptimizer;
private AwaitOverrider() { }
internal static void Create<TAwaiter>() where TAwaiter : struct, T, ICriticalNotifyCompletion, IPromiseAwaiter
{
awaitOptimizer = new Impl<TAwaiter>();
}
internal abstract void AwaitOnCompleted(ref T awaiter, Internal.AsyncPromiseRef asyncPromiseRef);
private sealed class Impl<TAwaiter> : AwaitOverrider<T> where TAwaiter : struct, T, IPromiseAwaiter
{
internal override void AwaitOnCompleted(ref T awaiter, Internal.AsyncPromiseRef asyncPromiseRef)
{
if (typeof(T) == typeof(TAwaiter))
{
Unsafe.As<T, TAwaiter>(ref awaiter).AwaitOnCompleted(asyncPromiseRef);
return;
}
throw new System.InvalidOperationException("type T {" + typeof(T).FullName + "} is not TAwaiter {" + typeof(TAwaiter).FullName + "}");
}
}
}
This is the meat of how it works. typeof(T) == typeof(TAwaiter)
is already optimized away by the JIT/AOT compiler. This also takes advantage of Unsafe.As
. I'm not sure the best way of using that in Unity, if there's a way to link to the Unsafe
assembly as a dependency without including the dll for old runtimes. If necessary, we can do a (TAwaiter)(object) awaiter
cast (.Net 4.0+ optimizes that operation to not box, though we still end up making a copy of the struct).
static PromiseAwaiter()
{
AwaitOverrider<PromiseAwaiter<T>>.Create<PromiseAwaiter<T>>();
}
And this is how the awaiters actually subscribe themselves to the AwaitOverrider
while still working in AOT/IL2CPP environments without reflection.
from protopromise.
I actually think I figured out a cleaner way to handle this without Unsafe.As
(though the type checks are still necessary in the builder):
internal abstract class AwaitOverrider<T> where T : INotifyCompletion
{
internal static AwaitOverrider<T> awaitOptimizer;
internal abstract void AwaitOnCompleted(ref T awaiter, Internal.AsyncPromiseRef asyncPromiseRef);
}
internal sealed class AwaitOverriderImpl<TAwaiter> : AwaitOverrider<TAwaiter> where TAwaiter : struct, ICriticalNotifyCompletion, IPromiseAwaiter
{
internal static void Create()
{
awaitOptimizer = new AwaitOverriderImpl<TAwaiter>();
}
internal override void AwaitOnCompleted(ref TAwaiter awaiter, Internal.AsyncPromiseRef asyncPromiseRef)
{
awaiter.AwaitOnCompleted(asyncPromiseRef);
}
}
static PromiseAwaiter()
{
AwaitOverriderImpl<PromiseAwaiter<T>>.Create();
}
from protopromise.
It is unfortunate that we have to resort to a virtual function call here and allocate an object to enable the virtual call, when the call itself should be directly callable. If the compiler would allow method overloads in the AsyncMethodBuilder, we could add an overload very easily without all this extra ceremony:
public void AwaitOnCompleted<T, TStateMachine>(ref PromiseAwaiter<T> awaiter, ref TStateMachine stateMachine)
where TStateMachine : IAsyncStateMachine
{
awaiter.AwaitOnCompleted(_promise);
}
But that's not possible (the compiler-generated state machine bakes in the default method call).
We could use a delegate instead of a class with a virtual function, but that still has to allocate the delegate and actually costs more than a virtual call.
C# 9 added function pointers, which we can take advantage of to remove the allocation and extra overhead of virtual function/delegates, so it just calls the function directly via its pointer:
internal abstract unsafe class AwaitOverrider<T> where T : INotifyCompletion
{
internal static delegate*<ref T, Internal.AsyncPromiseRef, void> action;
}
internal sealed unsafe class AwaitOverriderImpl<TAwaiter> : AwaitOverrider<TAwaiter> where TAwaiter : struct, ICriticalNotifyCompletion, IPromiseAwaiter
{
internal static void Create()
{
action = &AwaitOnCompleted;
}
private static void AwaitOnCompleted(ref TAwaiter awaiter, Internal.AsyncPromiseRef asyncPromiseRef)
{
awaiter.AwaitOnCompleted(asyncPromiseRef);
}
}
Unfortunately, the call cannot be in-lined, as it's determined at runtime, but I think that's the best we can do until/unless the compiler ever allows overloads in the builder.
from protopromise.
According to Stephen Toub, the JIT will optimize away ((IPromiseAwaiter)awaiter).AwaitOnCompleted(_promise)
to not box, so we can simplify the builder method:
public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : INotifyCompletion
where TStateMachine : IAsyncStateMachine
{
if (typeof(TAwaiter).IsValueType && typeof(IPromiseAwaiter).IsAssignableFrom(typeof(TAwaiter)))
{
((IPromiseAwaiter)awaiter).AwaitOnCompleted(_promise);
}
else
{
awaiter.OnCompleted(_promise.GetContinuation(ref stateMachine));
}
}
But, we can't rely on that optimization in Unity with Mono or IL2CPP, so we still have to use the AwaitOverrider method to avoid boxing. We can #ifdef to use AwaitOverrider for Unity and this method for other runtimes.
from protopromise.
I'm thinking of pulling this into v2.0 to be able to create a nuget package with progress enabled (#51). It wouldn't make much sense to have progress enabled by default without async Promise
progress support. I will need to do some initial work to see if this can be done like ((IPromiseAwaiter)awaiter).AwaitOnCompleted(_promise)
without boxing in Unity which would make this much easier.
[Edit] I just tested in latest Unity, and it still doesn't optimize away the box there (it still allocates). Looks like I'll have to do this the hard way.
from protopromise.
Related Issues (20)
- Separate Nuget package for Unity helpers
- Test failures in IL2CPP
- Test failure HOT 1
- Add async void method builder override HOT 2
- NullReferenceException
- Add `PromiseEnumerable` HOT 5
- Unity Warnings (Demo Scripts exists outside the Asset folder) HOT 2
- Concurrency failure in CI
- Add additional async coordination primitives HOT 1
- Issue with WaitForResult HOT 6
- IL2CPP crash HOT 1
- Checking `CancelationToken.IsCancelationRequested` returns wrong value
- A linked `CancelationSource` is not canceled from `CancellationTokenSource` HOT 1
- Improve readability of readme
- Promises wrapper for Unity's AssetBundle.LoadAssetAsync()? HOT 8
- `PromiseYielder.WaitOneFrame()` waits an extra frame.
- PromiseBehaviour warning when not reloading domain HOT 2
- Prefix `bool`-returning methods with `Try`
- `TryWait` with an already canceled token does not release the lock. HOT 1
- IndexOutOfRangeException on iOS? HOT 14
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from protopromise.