diff --git a/src/UniTask.NetCore/Linq/AppendPrepend.cs b/src/UniTask.NetCore/Linq/AppendPrepend.cs index 8b3a135..960e113 100644 --- a/src/UniTask.NetCore/Linq/AppendPrepend.cs +++ b/src/UniTask.NetCore/Linq/AppendPrepend.cs @@ -110,26 +110,28 @@ namespace Cysharp.Threading.Tasks.Linq { var self = (Enumerator)state; - if (self.awaiter.GetResult()) + if (self.TryGetResult(self.awaiter, out var result)) { - self.Current = self.enumerator.Current; - } - else - { - if (self.state == State.RequireAppend) + if (result) { - self.state = State.Completed; - self.Current = self.element; + self.Current = self.enumerator.Current; + self.completionSource.TrySetResult(true); } else { - self.state = State.Completed; - self.completionSource.TrySetResult(false); - return; + if (self.state == State.RequireAppend) + { + self.state = State.Completed; + self.Current = self.element; + self.completionSource.TrySetResult(true); + } + else + { + self.state = State.Completed; + self.completionSource.TrySetResult(false); + } } } - - self.completionSource.TrySetResult(true); } public UniTask DisposeAsync() @@ -144,770 +146,3 @@ namespace Cysharp.Threading.Tasks.Linq } } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/UniTask.NetCore/Linq/AsyncEnumeratorBase.cs b/src/UniTask.NetCore/Linq/AsyncEnumeratorBase.cs index 3befbef..6813a7d 100644 --- a/src/UniTask.NetCore/Linq/AsyncEnumeratorBase.cs +++ b/src/UniTask.NetCore/Linq/AsyncEnumeratorBase.cs @@ -31,6 +31,21 @@ namespace Cysharp.Threading.Tasks.Linq { completionSource.GetResult(token); } + + protected bool TryGetResult(UniTask.Awaiter awaiter, out T result) + { + try + { + result = awaiter.GetResult(); + return true; + } + catch (Exception ex) + { + completionSource.TrySetException(ex); + result = default; + return false; + } + } } diff --git a/src/UniTask.NetCore/Linq/Cast.cs b/src/UniTask.NetCore/Linq/Cast.cs index 413d127..9a27f72 100644 --- a/src/UniTask.NetCore/Linq/Cast.cs +++ b/src/UniTask.NetCore/Linq/Cast.cs @@ -1,775 +1,53 @@ -namespace Cysharp.Threading.Tasks.Linq +using Cysharp.Threading.Tasks.Internal; +using System; +using System.Threading; + +namespace Cysharp.Threading.Tasks.Linq { - internal sealed class Cast + public static partial class UniTaskAsyncEnumerable { + public static IUniTaskAsyncEnumerable Cast(this IUniTaskAsyncEnumerable source) + { + Error.ThrowArgumentNullException(source, nameof(source)); + + return new Cast(source); + } } - -} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + internal sealed class Cast : IUniTaskAsyncEnumerable + { + readonly IUniTaskAsyncEnumerable source; + + public Cast(IUniTaskAsyncEnumerable source) + { + this.source = source; + } + + public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new Enumerator(source, cancellationToken); + } + + class Enumerator : AsyncEnumeratorBase + { + public Enumerator(IUniTaskAsyncEnumerable source, CancellationToken cancellationToken) + + : base(source, cancellationToken) + { + } + + protected override bool TryMoveNextCore(bool sourceHasCurrent, out bool result) + { + if (sourceHasCurrent) + { + Current = (TResult)SourceCurrent; + result = true; + return true; + } + + result = false; + return true; + } + } + } +} \ No newline at end of file diff --git a/src/UniTask.NetCore/Linq/Concat.cs b/src/UniTask.NetCore/Linq/Concat.cs index 6e3dfa4..45aab70 100644 --- a/src/UniTask.NetCore/Linq/Concat.cs +++ b/src/UniTask.NetCore/Linq/Concat.cs @@ -86,7 +86,15 @@ namespace Cysharp.Threading.Tasks.Linq } } - awaiter = enumerator.MoveNextAsync().GetAwaiter(); + try + { + awaiter = enumerator.MoveNextAsync().GetAwaiter(); + } + catch (Exception ex) + { + completionSource.TrySetException(ex); + return; + } if (awaiter.IsCompleted) { @@ -102,21 +110,24 @@ namespace Cysharp.Threading.Tasks.Linq { var self = (Enumerator)state; - if (self.awaiter.GetResult()) + if (self.TryGetResult(self.awaiter, out var result)) { - self.Current = self.enumerator.Current; - self.completionSource.TrySetResult(true); - } - else - { - if (self.iteratingState == IteratingState.IteratingFirst) + if (result) { - self.RunSecondAfterDisposeAsync().Forget(); - return; + self.Current = self.enumerator.Current; + self.completionSource.TrySetResult(true); } + else + { + if (self.iteratingState == IteratingState.IteratingFirst) + { + self.RunSecondAfterDisposeAsync().Forget(); + return; + } - self.iteratingState = IteratingState.Complete; - self.completionSource.TrySetResult(false); + self.iteratingState = IteratingState.Complete; + self.completionSource.TrySetResult(false); + } } } diff --git a/src/UniTask.NetCore/Linq/Create.cs b/src/UniTask.NetCore/Linq/Create.cs deleted file mode 100644 index d853334..0000000 --- a/src/UniTask.NetCore/Linq/Create.cs +++ /dev/null @@ -1,775 +0,0 @@ -namespace Cysharp.Threading.Tasks.Linq -{ - internal sealed class Create - { - } - - -} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/UniTask.NetCore/Linq/DefaultIfEmpty.cs b/src/UniTask.NetCore/Linq/DefaultIfEmpty.cs index 49ca694..756fd5a 100644 --- a/src/UniTask.NetCore/Linq/DefaultIfEmpty.cs +++ b/src/UniTask.NetCore/Linq/DefaultIfEmpty.cs @@ -1,775 +1,135 @@ -namespace Cysharp.Threading.Tasks.Linq +using Cysharp.Threading.Tasks.Internal; +using System; +using System.Threading; + +namespace Cysharp.Threading.Tasks.Linq { - internal sealed class DefaultIfEmpty + public static partial class UniTaskAsyncEnumerable { + public static IUniTaskAsyncEnumerable DefaultIfEmpty(this IUniTaskAsyncEnumerable source) + { + Error.ThrowArgumentNullException(source, nameof(source)); + + return new DefaultIfEmpty(source, default); + } + + public static IUniTaskAsyncEnumerable DefaultIfEmpty(this IUniTaskAsyncEnumerable source, TSource defaultValue) + { + Error.ThrowArgumentNullException(source, nameof(source)); + + return new DefaultIfEmpty(source, defaultValue); + } } + internal sealed class DefaultIfEmpty : IUniTaskAsyncEnumerable + { + readonly IUniTaskAsyncEnumerable source; + readonly TSource defaultValue; + + public DefaultIfEmpty(IUniTaskAsyncEnumerable source, TSource defaultValue) + { + this.source = source; + this.defaultValue = defaultValue; + } + + public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new Enumerator(source, defaultValue, cancellationToken); + } + + sealed class Enumerator : MoveNextSource, IUniTaskAsyncEnumerator + { + enum IteratingState : byte + { + Empty, + Iterating, + Completed + } + + static readonly Action MoveNextCoreDelegate = MoveNextCore; + + readonly IUniTaskAsyncEnumerable source; + readonly TSource defaultValue; + CancellationToken cancellationToken; + + IteratingState iteratingState; + IUniTaskAsyncEnumerator enumerator; + UniTask.Awaiter awaiter; + + public Enumerator(IUniTaskAsyncEnumerable source, TSource defaultValue, CancellationToken cancellationToken) + { + this.source = source; + this.defaultValue = defaultValue; + this.cancellationToken = cancellationToken; + + this.iteratingState = IteratingState.Empty; + } + + public TSource Current { get; private set; } + + + public UniTask MoveNextAsync() + { + cancellationToken.ThrowIfCancellationRequested(); + completionSource.Reset(); + + if (iteratingState == IteratingState.Empty) + { + return CompletedTasks.False; + } + + awaiter = enumerator.MoveNextAsync().GetAwaiter(); + + if (awaiter.IsCompleted) + { + MoveNextCore(this); + } + else + { + awaiter.SourceOnCompleted(MoveNextCoreDelegate, this); + } + + return new UniTask(this, completionSource.Version); + } + + static void MoveNextCore(object state) + { + var self = (Enumerator)state; + + if (self.TryGetResult(self.awaiter, out var result)) + { + if (result) + { + self.iteratingState = IteratingState.Iterating; + self.Current = self.enumerator.Current; + self.completionSource.TrySetResult(true); + } + else + { + if (self.iteratingState == IteratingState.Empty) + { + self.iteratingState = IteratingState.Completed; + + self.Current = self.defaultValue; + self.completionSource.TrySetResult(true); + } + else + { + self.completionSource.TrySetResult(false); + } + } + } + } + + public UniTask DisposeAsync() + { + if (enumerator != null) + { + return enumerator.DisposeAsync(); + } + return default; + } + } + } } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/UniTask.NetCore/Linq/OfType.cs b/src/UniTask.NetCore/Linq/OfType.cs index b49404c..213cc98 100644 --- a/src/UniTask.NetCore/Linq/OfType.cs +++ b/src/UniTask.NetCore/Linq/OfType.cs @@ -1,775 +1,61 @@ -namespace Cysharp.Threading.Tasks.Linq +using Cysharp.Threading.Tasks.Internal; +using System; +using System.Threading; + +namespace Cysharp.Threading.Tasks.Linq { - internal sealed class OfType + public static partial class UniTaskAsyncEnumerable { + public static IUniTaskAsyncEnumerable OfType(this IUniTaskAsyncEnumerable source) + { + Error.ThrowArgumentNullException(source, nameof(source)); + + return new OfType(source); + } } - -} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + internal sealed class OfType : IUniTaskAsyncEnumerable + { + readonly IUniTaskAsyncEnumerable source; + + public OfType(IUniTaskAsyncEnumerable source) + { + this.source = source; + } + + public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new Enumerator(source, cancellationToken); + } + + class Enumerator : AsyncEnumeratorBase + { + public Enumerator(IUniTaskAsyncEnumerable source, CancellationToken cancellationToken) + + : base(source, cancellationToken) + { + } + + protected override bool TryMoveNextCore(bool sourceHasCurrent, out bool result) + { + if (sourceHasCurrent) + { + if (SourceCurrent is TResult castCurent) + { + Current = castCurent; + result = true; + return true; + } + else + { + result = default; + return false; + } + } + + result = false; + return true; + } + } + } +} \ No newline at end of file diff --git a/src/UniTask.NetCore/Linq/Throw.cs b/src/UniTask.NetCore/Linq/Throw.cs new file mode 100644 index 0000000..a939559 --- /dev/null +++ b/src/UniTask.NetCore/Linq/Throw.cs @@ -0,0 +1,54 @@ +using Cysharp.Threading.Tasks.Internal; +using System; +using System.Threading; + +namespace Cysharp.Threading.Tasks.Linq +{ + public static partial class UniTaskAsyncEnumerable + { + public static IUniTaskAsyncEnumerable Throw(Exception exception) + { + return new Throw(exception); + } + } + + internal class Throw : IUniTaskAsyncEnumerable + { + readonly Exception exception; + + public Throw(Exception exception) + { + this.exception = exception; + } + + public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new Enumerator(exception, cancellationToken); + } + + class Enumerator : IUniTaskAsyncEnumerator + { + readonly Exception exception; + CancellationToken cancellationToken; + + public Enumerator(Exception exception, CancellationToken cancellationToken) + { + this.exception = exception; + this.cancellationToken = cancellationToken; + } + + public TValue Current => default; + + public UniTask MoveNextAsync() + { + cancellationToken.ThrowIfCancellationRequested(); + return UniTask.FromException(exception); + } + + public UniTask DisposeAsync() + { + return default; + } + } + } +} \ No newline at end of file diff --git a/src/UniTask.NetCore/Linq/ToUniTaskAsyncEnumerable.cs b/src/UniTask.NetCore/Linq/ToUniTaskAsyncEnumerable.cs index 435eb9c..500ce13 100644 --- a/src/UniTask.NetCore/Linq/ToUniTaskAsyncEnumerable.cs +++ b/src/UniTask.NetCore/Linq/ToUniTaskAsyncEnumerable.cs @@ -243,19 +243,21 @@ namespace Cysharp.Threading.Tasks.Linq { get { - if (error != null) - { - ExceptionDispatchInfo.Capture(error).Throw(); - } - if (useCachedCurrent) { return current; } - current = queuedResult.Dequeue(); - useCachedCurrent = true; - return current; + if (queuedResult.Count != 0) + { + current = queuedResult.Dequeue(); + useCachedCurrent = true; + return current; + } + else + { + return default; // undefined. + } } } diff --git a/src/UniTask.NetCore/Linq/Where.cs b/src/UniTask.NetCore/Linq/Where.cs index 6d2a07d..05a1ed7 100644 --- a/src/UniTask.NetCore/Linq/Where.cs +++ b/src/UniTask.NetCore/Linq/Where.cs @@ -1,18 +1,57 @@ -using System; +using Cysharp.Threading.Tasks.Internal; +using System; using System.Threading; namespace Cysharp.Threading.Tasks.Linq { public static partial class UniTaskAsyncEnumerable { - public static IUniTaskAsyncEnumerable Where(this IUniTaskAsyncEnumerable source, Func predicate) + public static IUniTaskAsyncEnumerable Where(this IUniTaskAsyncEnumerable source, Func predicate) { - return new Cysharp.Threading.Tasks.Linq.Where(source, predicate); + Error.ThrowArgumentNullException(source, nameof(source)); + Error.ThrowArgumentNullException(predicate, nameof(predicate)); + + return new Where(source, predicate); } - public static IUniTaskAsyncEnumerable WhereAwait(this IUniTaskAsyncEnumerable source, Func> predicate) + public static IUniTaskAsyncEnumerable Where(this IUniTaskAsyncEnumerable source, Func predicate) { - return new Cysharp.Threading.Tasks.Linq.WhereAwait(source, predicate); + Error.ThrowArgumentNullException(source, nameof(source)); + Error.ThrowArgumentNullException(predicate, nameof(predicate)); + + return new WhereInt(source, predicate); + } + + public static IUniTaskAsyncEnumerable WhereAwait(this IUniTaskAsyncEnumerable source, Func> predicate) + { + Error.ThrowArgumentNullException(source, nameof(source)); + Error.ThrowArgumentNullException(predicate, nameof(predicate)); + + return new WhereAwait(source, predicate); + } + + public static IUniTaskAsyncEnumerable WhereAwait(this IUniTaskAsyncEnumerable source, Func> predicate) + { + Error.ThrowArgumentNullException(source, nameof(source)); + Error.ThrowArgumentNullException(predicate, nameof(predicate)); + + return new WhereAwaitInt(source, predicate); + } + + public static IUniTaskAsyncEnumerable WhereAwaitWithCancellation(this IUniTaskAsyncEnumerable source, Func> predicate) + { + Error.ThrowArgumentNullException(source, nameof(source)); + Error.ThrowArgumentNullException(predicate, nameof(predicate)); + + return new WhereAwaitCancellation(source, predicate); + } + + public static IUniTaskAsyncEnumerable WhereAwaitWithCancellation(this IUniTaskAsyncEnumerable source, Func> predicate) + { + Error.ThrowArgumentNullException(source, nameof(source)); + Error.ThrowArgumentNullException(predicate, nameof(predicate)); + + return new WhereAwaitIntCancellation(source, predicate); } } @@ -66,6 +105,57 @@ namespace Cysharp.Threading.Tasks.Linq } } + internal sealed class WhereInt : IUniTaskAsyncEnumerable + { + readonly IUniTaskAsyncEnumerable source; + readonly Func predicate; + + public WhereInt(IUniTaskAsyncEnumerable source, Func predicate) + { + this.source = source; + this.predicate = predicate; + } + + public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new Enumerator(source, predicate, cancellationToken); + } + + class Enumerator : AsyncEnumeratorBase + { + readonly Func predicate; + int index; + + public Enumerator(IUniTaskAsyncEnumerable source, Func predicate, CancellationToken cancellationToken) + + : base(source, cancellationToken) + { + this.predicate = predicate; + } + + protected override bool TryMoveNextCore(bool sourceHasCurrent, out bool result) + { + if (sourceHasCurrent) + { + if (predicate(SourceCurrent, checked(index++))) + { + Current = SourceCurrent; + result = true; + return true; + } + else + { + result = default; + return false; + } + } + + result = false; + return true; + } + } + } + internal sealed class WhereAwait : IUniTaskAsyncEnumerable { readonly IUniTaskAsyncEnumerable source; @@ -113,4 +203,149 @@ namespace Cysharp.Threading.Tasks.Linq } } + internal sealed class WhereAwaitInt : IUniTaskAsyncEnumerable + { + readonly IUniTaskAsyncEnumerable source; + readonly Func> predicate; + + public WhereAwaitInt(IUniTaskAsyncEnumerable source, Func> predicate) + { + this.source = source; + this.predicate = predicate; + } + + public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new Enumerator(source, predicate, cancellationToken); + } + + class Enumerator : AsyncEnumeratorAwaitSelectorBase + { + readonly Func> predicate; + int index; + + public Enumerator(IUniTaskAsyncEnumerable source, Func> predicate, CancellationToken cancellationToken) + + : base(source, cancellationToken) + { + this.predicate = predicate; + } + + protected override UniTask TransformAsync(TSource sourceCurrent) + { + return predicate(sourceCurrent, checked(index++)); + } + + protected override bool TrySetCurrentCore(bool awaitResult) + { + if (awaitResult) + { + Current = SourceCurrent; + return true; + } + else + { + return false; + } + } + } + } + + + + internal sealed class WhereAwaitCancellation : IUniTaskAsyncEnumerable + { + readonly IUniTaskAsyncEnumerable source; + readonly Func> predicate; + + public WhereAwaitCancellation(IUniTaskAsyncEnumerable source, Func> predicate) + { + this.source = source; + this.predicate = predicate; + } + + public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new Enumerator(source, predicate, cancellationToken); + } + + class Enumerator : AsyncEnumeratorAwaitSelectorBase + { + readonly Func> predicate; + + public Enumerator(IUniTaskAsyncEnumerable source, Func> predicate, CancellationToken cancellationToken) + + : base(source, cancellationToken) + { + this.predicate = predicate; + } + + protected override UniTask TransformAsync(TSource sourceCurrent) + { + return predicate(sourceCurrent, cancellationToken); + } + + protected override bool TrySetCurrentCore(bool awaitResult) + { + if (awaitResult) + { + Current = SourceCurrent; + return true; + } + else + { + return false; + } + } + } + } + + internal sealed class WhereAwaitIntCancellation : IUniTaskAsyncEnumerable + { + readonly IUniTaskAsyncEnumerable source; + readonly Func> predicate; + + public WhereAwaitIntCancellation(IUniTaskAsyncEnumerable source, Func> predicate) + { + this.source = source; + this.predicate = predicate; + } + + public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new Enumerator(source, predicate, cancellationToken); + } + + class Enumerator : AsyncEnumeratorAwaitSelectorBase + { + readonly Func> predicate; + int index; + + public Enumerator(IUniTaskAsyncEnumerable source, Func> predicate, CancellationToken cancellationToken) + + : base(source, cancellationToken) + { + this.predicate = predicate; + } + + protected override UniTask TransformAsync(TSource sourceCurrent) + { + return predicate(sourceCurrent, checked(index++), cancellationToken); + } + + protected override bool TrySetCurrentCore(bool awaitResult) + { + if (awaitResult) + { + Current = SourceCurrent; + return true; + } + else + { + return false; + } + } + } + } + } \ No newline at end of file diff --git a/src/UniTask.NetCore/Linq/Zip.cs b/src/UniTask.NetCore/Linq/Zip.cs index 31dd547..e3fdefb 100644 --- a/src/UniTask.NetCore/Linq/Zip.cs +++ b/src/UniTask.NetCore/Linq/Zip.cs @@ -90,22 +90,34 @@ namespace Cysharp.Threading.Tasks.Linq { var self = (Enumerator)state; - if (self.firstAwaiter.GetResult()) + if (self.TryGetResult(self.firstAwaiter, out var result)) { - self.secondAwaiter = self.secondEnumerator.MoveNextAsync().GetAwaiter(); - if (self.secondAwaiter.IsCompleted) + if (result) { - SecondMoveNextCore(self); + try + { + self.secondAwaiter = self.secondEnumerator.MoveNextAsync().GetAwaiter(); + } + catch (Exception ex) + { + self.completionSource.TrySetException(ex); + return; + } + + if (self.secondAwaiter.IsCompleted) + { + SecondMoveNextCore(self); + } + else + { + self.secondAwaiter.SourceOnCompleted(secondMoveNextCoreDelegate, self); + } } else { - self.secondAwaiter.SourceOnCompleted(secondMoveNextCoreDelegate, self); + self.completionSource.TrySetResult(false); } } - else - { - self.completionSource.TrySetResult(false); - } } static void SecondMoveNextCore(object state) diff --git a/src/UniTask.NetCore/Linq/_FileMaker.cs b/src/UniTask.NetCore/Linq/_FileMaker.cs index c8f6d3d..41494b1 100644 --- a/src/UniTask.NetCore/Linq/_FileMaker.cs +++ b/src/UniTask.NetCore/Linq/_FileMaker.cs @@ -24,24 +24,8 @@ namespace ___Dummy - public static IUniTaskAsyncEnumerable Cast(this IUniTaskAsyncEnumerable source) - { - throw new NotImplementedException(); - } - - - public static IUniTaskAsyncEnumerable DefaultIfEmpty(this IUniTaskAsyncEnumerable source) - { - throw new NotImplementedException(); - } - - public static IUniTaskAsyncEnumerable DefaultIfEmpty(this IUniTaskAsyncEnumerable source, TSource defaultValue) - { - throw new NotImplementedException(); - } - public static IUniTaskAsyncEnumerable Distinct(this IUniTaskAsyncEnumerable source) { throw new NotImplementedException(); @@ -259,11 +243,6 @@ namespace ___Dummy - public static IUniTaskAsyncEnumerable OfType(this IUniTaskAsyncEnumerable source) - { - throw new NotImplementedException(); - } - public static IOrderedAsyncEnumerable OrderBy(this IUniTaskAsyncEnumerable source, Func keySelector) { throw new NotImplementedException(); @@ -550,35 +529,7 @@ namespace ___Dummy throw new NotImplementedException(); } - public static IUniTaskAsyncEnumerable Where(this IUniTaskAsyncEnumerable source, Func predicate) - { - throw new NotImplementedException(); - } - - public static IUniTaskAsyncEnumerable Where(this IUniTaskAsyncEnumerable source, Func predicate) - { - throw new NotImplementedException(); - } - - public static IUniTaskAsyncEnumerable WhereAwait(this IUniTaskAsyncEnumerable source, Func> predicate) - { - throw new NotImplementedException(); - } - - public static IUniTaskAsyncEnumerable WhereAwait(this IUniTaskAsyncEnumerable source, Func> predicate) - { - throw new NotImplementedException(); - } - - public static IUniTaskAsyncEnumerable WhereAwaitWithCancellation(this IUniTaskAsyncEnumerable source, Func> predicate) - { - throw new NotImplementedException(); - } - - public static IUniTaskAsyncEnumerable WhereAwaitWithCancellation(this IUniTaskAsyncEnumerable source, Func> predicate) - { - throw new NotImplementedException(); - } + public static IUniTaskAsyncEnumerable> Zip(this IUniTaskAsyncEnumerable first, IUniTaskAsyncEnumerable second) { diff --git a/src/UniTask.NetCoreSandbox/Program.cs b/src/UniTask.NetCoreSandbox/Program.cs index 6344532..ae8d8c7 100644 --- a/src/UniTask.NetCoreSandbox/Program.cs +++ b/src/UniTask.NetCoreSandbox/Program.cs @@ -37,24 +37,28 @@ namespace NetCoreSandbox await Task.Delay(10, cancellationToken); } - static void Main(string[] args) + static async Task Main(string[] args) { - // Create Canceled token. - var cts = new CancellationTokenSource(); - cts.Cancel(); - - // OK, don't throw. - var e1 = FooAsync(cts.Token).GetAsyncEnumerator(cts.Token); - Console.WriteLine("OK:FooAsyunc().GetAsyncEnumerator()"); - - // Ix.Async LINQ Operator throws OperationCanceledException - var e2 = FooAsync(cts.Token).Select(x => x).GetAsyncEnumerator(cts.Token); + Console.WriteLine("YEAH"); + try + { + var xs = await UniTaskAsyncEnumerable.Range(1, 10).Concat(UniTaskAsyncEnumerable.Throw(new InvalidOperationException("something"))) + .SumAsync(); + } + catch (Exception ex) + { + Console.WriteLine("EX"); + Console.WriteLine(ex); + } } void Foo() { + + + // AsyncEnumerable.t var sb = new StringBuilder(); diff --git a/src/UniTask.NetCoreTests/Linq/Concat.cs b/src/UniTask.NetCoreTests/Linq/Concat.cs index e0e390a..2d6cdfa 100644 --- a/src/UniTask.NetCoreTests/Linq/Concat.cs +++ b/src/UniTask.NetCoreTests/Linq/Concat.cs @@ -27,6 +27,19 @@ namespace NetCoreTests.Linq xs.Should().BeEquivalentTo(ys); } + [Fact] + public async Task AppendThrow() + { + var xs = UniTaskTestException.ThrowImmediate().Append(99).ToArrayAsync(); + await Assert.ThrowsAsync(async () => await xs); + + var ys = UniTaskTestException.ThrowAfter().Append(99).ToArrayAsync(); + await Assert.ThrowsAsync(async () => await ys); + + var zs = UniTaskTestException.ThrowInMoveNext().Append(99).ToArrayAsync(); + await Assert.ThrowsAsync(async () => await zs); + } + [Theory] [InlineData(0, 0)] [InlineData(0, 1)] @@ -40,6 +53,19 @@ namespace NetCoreTests.Linq xs.Should().BeEquivalentTo(ys); } + [Fact] + public async Task PrependThrow() + { + var xs = UniTaskTestException.ThrowImmediate().Prepend(99).ToArrayAsync(); + await Assert.ThrowsAsync(async () => await xs); + + var ys = UniTaskTestException.ThrowAfter().Prepend(99).ToArrayAsync(); + await Assert.ThrowsAsync(async () => await ys); + + var zs = UniTaskTestException.ThrowInMoveNext().Prepend(99).ToArrayAsync(); + await Assert.ThrowsAsync(async () => await zs); + } + public static IEnumerable array1 = new object[][] { new object[] { (0, 0), (0, 0) }, // empty + empty @@ -61,5 +87,30 @@ namespace NetCoreTests.Linq var ys = l.Concat(r).ToArray(); xs.Should().BeEquivalentTo(ys); } + + [Fact] + public async Task ConcatThrow() + { + { + var xs = UniTaskTestException.ThrowImmediate().Concat(UniTaskAsyncEnumerable.Range(1, 10)).ToArrayAsync(); + await Assert.ThrowsAsync(async () => await xs); + + var ys = UniTaskTestException.ThrowAfter().Concat(UniTaskAsyncEnumerable.Range(1, 10)).ToArrayAsync(); + await Assert.ThrowsAsync(async () => await ys); + + var zs = UniTaskTestException.ThrowInMoveNext().Concat(UniTaskAsyncEnumerable.Range(1, 10)).ToArrayAsync(); + await Assert.ThrowsAsync(async () => await zs); + } + { + var xs = UniTaskAsyncEnumerable.Range(1, 10).Concat(UniTaskTestException.ThrowImmediate()).ToArrayAsync(); + await Assert.ThrowsAsync(async () => await xs); + + var ys = UniTaskAsyncEnumerable.Range(1, 10).Concat(UniTaskTestException.ThrowAfter()).ToArrayAsync(); + await Assert.ThrowsAsync(async () => await ys); + + var zs = UniTaskAsyncEnumerable.Range(1, 10).Concat(UniTaskTestException.ThrowInMoveNext()).ToArrayAsync(); + await Assert.ThrowsAsync(async () => await zs); + } + } } } diff --git a/src/UniTask.NetCoreTests/Linq/Filtering.cs b/src/UniTask.NetCoreTests/Linq/Filtering.cs new file mode 100644 index 0000000..c9fdd97 --- /dev/null +++ b/src/UniTask.NetCoreTests/Linq/Filtering.cs @@ -0,0 +1,118 @@ +using Cysharp.Threading.Tasks; +using Cysharp.Threading.Tasks.Linq; +using FluentAssertions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Concurrency; +using System.Reactive.Linq; +using System.Threading.Tasks; +using Xunit; + + +namespace NetCoreTests.Linq +{ + public class Filtering + { + [Fact] + public async Task Where() + { + var range = Enumerable.Range(1, 10); + var src = range.ToUniTaskAsyncEnumerable(); + + { + var a = await src.Where(x => x % 2 == 0).ToArrayAsync(); + var expected = range.Where(x => x % 2 == 0).ToArray(); + a.Should().BeEquivalentTo(expected); + } + { + var a = await src.Where((x, i) => (x + i) % 2 == 0).ToArrayAsync(); + var expected = range.Where((x, i) => (x + i) % 2 == 0).ToArray(); + a.Should().BeEquivalentTo(expected); + } + { + var a = await src.WhereAwait(x => UniTask.Run(() => x % 2 == 0)).ToArrayAsync(); + var b = await src.WhereAwait(x => UniTask.FromResult(x % 2 == 0)).ToArrayAsync(); + var expected = range.Where(x => x % 2 == 0).ToArray(); + a.Should().BeEquivalentTo(expected); + b.Should().BeEquivalentTo(expected); + } + { + var a = await src.WhereAwait((x, i) => UniTask.Run(() => (x + i) % 2 == 0)).ToArrayAsync(); + var b = await src.WhereAwait((x, i) => UniTask.FromResult((x + i) % 2 == 0)).ToArrayAsync(); + var expected = range.Where((x, i) => (x + i) % 2 == 0).ToArray(); + a.Should().BeEquivalentTo(expected); + b.Should().BeEquivalentTo(expected); + } + } + + + [Fact] + public async Task WhereException() + { + foreach (var item in UniTaskTestException.Throws()) + { + { + var xs = item.Where(x => x % 2 == 0).ToArrayAsync(); + await Assert.ThrowsAsync(async () => await xs); + } + { + var xs = item.Where((x, i) => x % 2 == 0).ToArrayAsync(); + await Assert.ThrowsAsync(async () => await xs); + } + { + var xs = item.WhereAwait(x => UniTask.FromResult(x % 2 == 0)).ToArrayAsync(); + await Assert.ThrowsAsync(async () => await xs); + } + { + var xs = item.WhereAwait((x, i) => UniTask.FromResult(x % 2 == 0)).ToArrayAsync(); + await Assert.ThrowsAsync(async () => await xs); + } + } + } + + [Fact] + public async Task OfType() + { + var data = new object[] { 0, null, 10, 30, null, "foo", 99 }; + + var a = await data.ToUniTaskAsyncEnumerable().OfType().ToArrayAsync(); + var b = data.OfType().ToArray(); + + a.Should().BeEquivalentTo(b); + } + + + [Fact] + public async Task OfTypeException() + { + foreach (var item in UniTaskTestException.Throws()) + { + var xs = item.Select(x => (object)x).OfType().ToArrayAsync(); + await Assert.ThrowsAsync(async () => await xs); + } + } + + [Fact] + public async Task Cast() + { + var data = new object[] { 0, 10, 30, 99 }; + + var a = await data.ToUniTaskAsyncEnumerable().Cast().ToArrayAsync(); + var b = data.Cast().ToArray(); + + a.Should().BeEquivalentTo(b); + } + + + [Fact] + public async Task CastException() + { + foreach (var item in UniTaskTestException.Throws()) + { + var xs = item.Select(x => (object)x).Cast().ToArrayAsync(); + await Assert.ThrowsAsync(async () => await xs); + } + } + } +} diff --git a/src/UniTask.NetCoreTests/Linq/_Exception.cs b/src/UniTask.NetCoreTests/Linq/_Exception.cs new file mode 100644 index 0000000..1e977cc --- /dev/null +++ b/src/UniTask.NetCoreTests/Linq/_Exception.cs @@ -0,0 +1,126 @@ +using Cysharp.Threading.Tasks; +using Cysharp.Threading.Tasks.Linq; +using System; +using System.Collections.Generic; +using System.Runtime.ExceptionServices; +using System.Threading; + +namespace NetCoreTests.Linq +{ + public class UniTaskTestException : Exception + { + public static IUniTaskAsyncEnumerable ThrowImmediate() + { + return UniTaskAsyncEnumerable.Throw(new UniTaskTestException()); + } + public static IUniTaskAsyncEnumerable ThrowAfter() + { + return new ThrowAfter(new UniTaskTestException()); + } + public static IUniTaskAsyncEnumerable ThrowInMoveNext() + { + return new ThrowIn(new UniTaskTestException()); + } + + + public static IEnumerable> Throws() + { + yield return ThrowImmediate(); + yield return ThrowAfter(); + yield return ThrowInMoveNext(); + yield return UniTaskAsyncEnumerable.Range(1, 3).Concat(ThrowImmediate()); + yield return UniTaskAsyncEnumerable.Range(1, 3).Concat(ThrowAfter()); + yield return UniTaskAsyncEnumerable.Range(1, 3).Concat(ThrowInMoveNext()); + } + } + + internal class ThrowIn : IUniTaskAsyncEnumerable + { + readonly Exception exception; + + public ThrowIn(Exception exception) + { + this.exception = exception; + } + + public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new Enumerator(exception, cancellationToken); + } + + class Enumerator : IUniTaskAsyncEnumerator + { + readonly Exception exception; + CancellationToken cancellationToken; + + public Enumerator(Exception exception, CancellationToken cancellationToken) + { + this.exception = exception; + this.cancellationToken = cancellationToken; + } + + public TValue Current => default; + + public UniTask MoveNextAsync() + { + ExceptionDispatchInfo.Capture(exception).Throw(); + return new UniTask(false); + } + + public UniTask DisposeAsync() + { + return default; + } + } + } + + internal class ThrowAfter : IUniTaskAsyncEnumerable + { + readonly Exception exception; + + public ThrowAfter(Exception exception) + { + this.exception = exception; + } + + public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new Enumerator(exception, cancellationToken); + } + + class Enumerator : IUniTaskAsyncEnumerator + { + readonly Exception exception; + CancellationToken cancellationToken; + + public Enumerator(Exception exception, CancellationToken cancellationToken) + { + this.exception = exception; + this.cancellationToken = cancellationToken; + } + + public TValue Current => default; + + public UniTask MoveNextAsync() + { + cancellationToken.ThrowIfCancellationRequested(); + + var tcs = new UniTaskCompletionSource(); + + var awaiter = UniTask.Yield().GetAwaiter(); + awaiter.UnsafeOnCompleted(() => + { + Thread.Sleep(1); + tcs.TrySetException(exception); + }); + + return tcs.Task; + } + + public UniTask DisposeAsync() + { + return default; + } + } + } +} diff --git a/src/UniTask/Assets/Plugins/UniTask/UniTaskScheduler.cs b/src/UniTask/Assets/Plugins/UniTask/UniTaskScheduler.cs index d725470..2f91f2a 100644 --- a/src/UniTask/Assets/Plugins/UniTask/UniTaskScheduler.cs +++ b/src/UniTask/Assets/Plugins/UniTask/UniTaskScheduler.cs @@ -70,7 +70,7 @@ namespace Cysharp.Threading.Tasks string msg = null; if (UnobservedExceptionWriteLogType != UnityEngine.LogType.Exception) { - msg = "UnobservedTaskException:" + ex.ToString(); + msg = "UnobservedTaskException: " + ex.ToString(); } switch (UnobservedExceptionWriteLogType) { @@ -93,7 +93,7 @@ namespace Cysharp.Threading.Tasks break; } #else - Console.WriteLine(ex.ToString()); + Console.WriteLine("UnobservedTaskException: " + ex.ToString()); #endif } }