C-Sharp-Promise
C-Sharp-Promise copied to clipboard
Instance of Promise is not awaitable
Make promise object awaitable for consistency with ES2017 standard
Why don't you just use a Task?
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.
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;
}
}
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.
For older .net versions, Microsoft.Bcl.Async might be an option.
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.
@MorganMoon hey mate, are you using this library with async/await
? :)
@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;
}
}
@MorganMoon awesome Morgan! Do you like to create a Pull Request of your changes to have them from the library?
@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?
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.
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?