Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Instance of Promise is not awaitable #76

Open
lm902 opened this issue May 30, 2018 · 12 comments
Open

Instance of Promise is not awaitable #76

lm902 opened this issue May 30, 2018 · 12 comments

Comments

@lm902
Copy link

lm902 commented May 30, 2018

Make promise object awaitable for consistency with ES2017 standard

@ashleydavis
Copy link
Contributor

Why don't you just use a Task?

@RoryDungan
Copy link
Contributor

In order to be an awaiter an object must implement the INotifyCompletion interface. This is only available in .NET Core, .NET Standard and .NET Framework 4.5+, but we still need to target .NET Framework 3.5 because that is what's used by Unity 3d.

Unity is gradually upgrading to support .NET Standard 2.0 and .NET Framwork 4.6, but only .NET Framework 3.5 is considered "stable" in the current Unity LTS release (2017.4), which will still be supported until 2020. Although Unity 2018.1 supports the new .NET framework, the older version is still the default.

This could be a useful feature but would need to be implemented in a way that still supports the older .NET versions.

@MorganMoon
Copy link

I just made an extension method called .Await() for IPromise that uses TaskCompletionSource. It can't be used in the framework officially because TaskCompletionSource is .NET Framework 4+... But, hopefully this helps you @lm902

Here:

    internal class PromiseAwaitable
    {
        private TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();

        public PromiseAwaitable(IPromise promise)
        {
            promise.Done(OnCompleted, OnRejected);
        }

        private void OnCompleted()
        {
            _taskCompletionSource.TrySetResult(true);
        }

        private void OnRejected(Exception ex)
        {
            _taskCompletionSource.TrySetException(ex);
        }

        public Task Run()
        {
            return _taskCompletionSource.Task;
        }
    }

    internal class PromiseAwaitable<T>
    {
        private TaskCompletionSource<T> _taskCompletionSource = new TaskCompletionSource<T>();

        public PromiseAwaitable(IPromise<T> promise)
        {
            promise.Done(OnFinished, OnRejected);
        }

        private void OnFinished(T returnValue)
        {
            _taskCompletionSource.TrySetResult(returnValue);
        }

        private void OnRejected(Exception ex)
        {
            _taskCompletionSource.TrySetException(ex);
        }

        public Task<T> Run()
        {
            return _taskCompletionSource.Task;
        }
    }
}

And the extension methods:

    public static class PromiseExtensions
    {
        public static async Task Await(this IPromise promiseToAwait)
        {
            await new PromiseAwaitable(promiseToAwait).Run();
        }

        public static async Task<PromiseT> Await<PromiseT>(this IPromise<PromiseT> promiseToAwait)
        {
            var awaitablePromise = await new PromiseAwaitable<PromiseT>(promiseToAwait).Run();
            return awaitablePromise;
        }
    }

@RoryDungan
Copy link
Contributor

That's a good idea. I didn't consider using extension methods for this. Once #26 is merged we may be able to use this approach to add the functionality to the main library if there's a clean way to include/exclude these classes depending on the .NET version.

@jornj
Copy link

jornj commented Jun 12, 2018

For older .net versions, Microsoft.Bcl.Async might be an option.

@RoryDungan
Copy link
Contributor

I've added support for .NET Standard 2.0 in addition to .NET Framework 3.5 so it should be possible to add this now, as long as it's conditionally disabled so it doesn't break the .NET 3.5 build.

@jdnichollsc
Copy link
Contributor

@MorganMoon hey mate, are you using this library with async/await? :)

@MorganMoon
Copy link

@jdnichollsc

@MorganMoon hey mate, are you using this library with async/await? :)

I am using this library in a Unity project, and there are some cases where I use it with async/await, however it is not often. I also use the extension methods below as a way to use a Task as a IPromise

public static class TaskExtensions
    {
        public static IPromise AsPromise(this Task taskToUseAsPromise)
        {
            var promise = new Promise();
            try
            {
                taskToUseAsPromise
                    .ContinueWith((t) =>
                    {
                        if (t.IsFaulted)
                        {
                            // faulted with exception
                            Exception ex = t.Exception;
                            while (ex is AggregateException && ex.InnerException != null)
                                ex = ex.InnerException;
                            promise.Reject(ex);
                        }
                        else if (t.IsCanceled)
                        {
                            promise.Reject(new PromiseCancelledException());
                        }
                        else
                        {
                            // completed successfully
                            promise.Resolve();
                        }
                    }, TaskScheduler.FromCurrentSynchronizationContext());
            }
            catch(Exception ex)
            {
                if(promise.CurState == PromiseState.Pending)
                {
                    promise.Reject(ex);
                }
            }
            return promise;
        }

        public static IPromise<TaskT> AsPromise<TaskT>(this Task<TaskT> taskToUseAsPromise)
        {
            var promise = new Promise<TaskT>();
            taskToUseAsPromise
                .ContinueWith((t) =>
                {
                    if (t.IsFaulted)
                    {
                        // faulted with exception
                        Exception ex = t.Exception;
                        while (ex is AggregateException && ex.InnerException != null)
                            ex = ex.InnerException;
                        promise.Reject(ex);
                    }
                    else if (t.IsCanceled)
                    {
                        promise.Reject(new PromiseCancelledException());
                    }
                    else
                    {
                        // completed successfully
                        promise.Resolve(t.Result);
                    }
                }, TaskScheduler.FromCurrentSynchronizationContext());
            return promise;
        }
    }

@jdnichollsc
Copy link
Contributor

@MorganMoon awesome Morgan! Do you like to create a Pull Request of your changes to have them from the library?

@jdnichollsc
Copy link
Contributor

@RoryDungan hello mate, one little question
Can we use C# conditional compilation to detect the version of .NET and apply the extension to support Async/Await?

And guys, What do you think about this https://github.com/Cysharp/UniTask?

@RoryDungan
Copy link
Contributor

Yeah conditional compliation would be a good solution if there's an easy way to set that up. I'd rather not add a dependency on UniTask though because some people do use this library in plain C#/.NET projects outside of Unity, so if there's a way to get that functionality without pulling in the whole other library as a dependency that would be ideal.

@JashanChittesh
Copy link

JashanChittesh commented Nov 16, 2020

In order to be an awaiter an object must implement the INotifyCompletion interface. This is only available in .NET Core, .NET Standard and .NET Framework 4.5+, but we still need to target .NET Framework 3.5 because that is what's used by Unity 3d.

Is that still the case? I believe the default LTS version of Unity is now 2019.4, which no longer has .NET Framework 3.5. Only 4.5+ and Standard 2.0.

I'm not perfectly sure if this is the right place but I ran into a peculiar situation that may be solved by promises being awaitable: I have a Promise that gets resolved from another thread, which breaks accessing Unity's methods (because the calls no longer come from the main thread). I have a (hacky) workaround by using Promise instead of IPromise, and then looping while CurState is Pending). Something like:

var promise = MethodThatResolvesPromiseFromAnotherThread();
// sync with main thread
while (promise.CurState == PromiseState.Pending) { await Task.Yield(); }
promise.Then(/* Do stuff on Unity's main thread */);

My thinking was that having Promise awaitable would let me do something like:

var promise = MethodThatResolvesPromiseFromAnotherThread();
await promise.Finish();
promise.Then(/* Do stuff on Unity's main thread */);

Would that be possible? Or am I misunderstanding what awaitable Promises are?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants