|
|
@@ -0,0 +1,174 @@
|
|
|
+// Licensed to the .NET Foundation under one or more agreements.
|
|
|
+// The .NET Foundation licenses this file to you under the Apache 2.0 License.
|
|
|
+// See the LICENSE file in the project root for more information.
|
|
|
+
|
|
|
+using System.Reactive;
|
|
|
+using System.Reactive.Concurrency;
|
|
|
+using System.Reactive.Linq;
|
|
|
+using System.Reactive.Subjects;
|
|
|
+
|
|
|
+namespace System.Threading.Tasks
|
|
|
+{
|
|
|
+ public static class TaskAsyncObservableExtensions
|
|
|
+ {
|
|
|
+ public static IAsyncObservable<Unit> ToAsyncObservable(this Task task)
|
|
|
+ {
|
|
|
+ if (task == null)
|
|
|
+ throw new ArgumentNullException(nameof(task));
|
|
|
+
|
|
|
+ return AsyncObservable.Create<Unit>(observer => task.AcceptAsync(observer));
|
|
|
+ }
|
|
|
+
|
|
|
+ public static IAsyncObservable<Unit> ToAsyncObservable(this Task task, IAsyncScheduler scheduler)
|
|
|
+ {
|
|
|
+ if (task == null)
|
|
|
+ throw new ArgumentNullException(nameof(task));
|
|
|
+ if (scheduler == null)
|
|
|
+ throw new ArgumentNullException(nameof(scheduler));
|
|
|
+
|
|
|
+ return AsyncObservable.Create<Unit>(observer => task.AcceptAsync(observer, scheduler));
|
|
|
+ }
|
|
|
+
|
|
|
+ public static IAsyncObservable<TResult> ToAsyncObservable<TResult>(this Task<TResult> task)
|
|
|
+ {
|
|
|
+ if (task == null)
|
|
|
+ throw new ArgumentNullException(nameof(task));
|
|
|
+
|
|
|
+ return AsyncObservable.Create<TResult>(observer => task.AcceptAsync(observer));
|
|
|
+ }
|
|
|
+
|
|
|
+ public static IAsyncObservable<TResult> ToAsyncObservable<TResult>(this Task<TResult> task, IAsyncScheduler scheduler)
|
|
|
+ {
|
|
|
+ if (task == null)
|
|
|
+ throw new ArgumentNullException(nameof(task));
|
|
|
+ if (scheduler == null)
|
|
|
+ throw new ArgumentNullException(nameof(scheduler));
|
|
|
+
|
|
|
+ return AsyncObservable.Create<TResult>(observer => task.AcceptAsync(observer, scheduler));
|
|
|
+ }
|
|
|
+
|
|
|
+ public static Task<IAsyncDisposable> AcceptAsync(this Task task, IAsyncObserver<Unit> observer) => AcceptAsync(task, observer, ImmediateAsyncScheduler.Instance);
|
|
|
+
|
|
|
+ public static Task<IAsyncDisposable> AcceptAsync(this Task task, IAsyncObserver<Unit> observer, IAsyncScheduler scheduler)
|
|
|
+ {
|
|
|
+ if (task == null)
|
|
|
+ throw new ArgumentNullException(nameof(task));
|
|
|
+ if (observer == null)
|
|
|
+ throw new ArgumentNullException(nameof(observer));
|
|
|
+ if (scheduler == null)
|
|
|
+ throw new ArgumentNullException(nameof(scheduler));
|
|
|
+
|
|
|
+ Task<IAsyncDisposable> CompleteAsync()
|
|
|
+ {
|
|
|
+ return scheduler.ScheduleAsync(async ct =>
|
|
|
+ {
|
|
|
+ if (ct.IsCancellationRequested)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (task.Status)
|
|
|
+ {
|
|
|
+ case TaskStatus.RanToCompletion:
|
|
|
+ await observer.OnNextAsync(Unit.Default).RendezVous(scheduler);
|
|
|
+ await observer.OnCompletedAsync().RendezVous(scheduler);
|
|
|
+ break;
|
|
|
+ case TaskStatus.Faulted:
|
|
|
+ await observer.OnErrorAsync(task.Exception.InnerException).RendezVous(scheduler);
|
|
|
+ break;
|
|
|
+ case TaskStatus.Canceled:
|
|
|
+ await observer.OnErrorAsync(new TaskCanceledException(task)).RendezVous(scheduler);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ Task<IAsyncDisposable> CoreAsync()
|
|
|
+ {
|
|
|
+ if (task.IsCompleted)
|
|
|
+ {
|
|
|
+ return CompleteAsync();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ var tco = TaskContinuationOptions.None;
|
|
|
+
|
|
|
+ if (scheduler == ImmediateAsyncScheduler.Instance)
|
|
|
+ {
|
|
|
+ tco = TaskContinuationOptions.ExecuteSynchronously;
|
|
|
+ }
|
|
|
+
|
|
|
+ var subject = new SequentialAsyncAsyncSubject<Unit>();
|
|
|
+
|
|
|
+ task.ContinueWith(t => CompleteAsync(), tco);
|
|
|
+
|
|
|
+ return subject.SubscribeAsync(observer);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return CoreAsync();
|
|
|
+ }
|
|
|
+
|
|
|
+ public static Task<IAsyncDisposable> AcceptAsync<TResult>(this Task<TResult> task, IAsyncObserver<TResult> observer) => AcceptAsync(task, observer, ImmediateAsyncScheduler.Instance);
|
|
|
+
|
|
|
+ public static Task<IAsyncDisposable> AcceptAsync<TResult>(this Task<TResult> task, IAsyncObserver<TResult> observer, IAsyncScheduler scheduler)
|
|
|
+ {
|
|
|
+ if (task == null)
|
|
|
+ throw new ArgumentNullException(nameof(task));
|
|
|
+ if (observer == null)
|
|
|
+ throw new ArgumentNullException(nameof(observer));
|
|
|
+ if (scheduler == null)
|
|
|
+ throw new ArgumentNullException(nameof(scheduler));
|
|
|
+
|
|
|
+ Task<IAsyncDisposable> CompleteAsync()
|
|
|
+ {
|
|
|
+ return scheduler.ScheduleAsync(async ct =>
|
|
|
+ {
|
|
|
+ if (ct.IsCancellationRequested)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (task.Status)
|
|
|
+ {
|
|
|
+ case TaskStatus.RanToCompletion:
|
|
|
+ await observer.OnNextAsync(task.Result).RendezVous(scheduler);
|
|
|
+ await observer.OnCompletedAsync().RendezVous(scheduler);
|
|
|
+ break;
|
|
|
+ case TaskStatus.Faulted:
|
|
|
+ await observer.OnErrorAsync(task.Exception.InnerException).RendezVous(scheduler);
|
|
|
+ break;
|
|
|
+ case TaskStatus.Canceled:
|
|
|
+ await observer.OnErrorAsync(new TaskCanceledException(task)).RendezVous(scheduler);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ Task<IAsyncDisposable> CoreAsync()
|
|
|
+ {
|
|
|
+ if (task.IsCompleted)
|
|
|
+ {
|
|
|
+ return CompleteAsync();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ var tco = TaskContinuationOptions.None;
|
|
|
+
|
|
|
+ if (scheduler == ImmediateAsyncScheduler.Instance)
|
|
|
+ {
|
|
|
+ tco = TaskContinuationOptions.ExecuteSynchronously;
|
|
|
+ }
|
|
|
+
|
|
|
+ var subject = new SequentialAsyncAsyncSubject<TResult>();
|
|
|
+
|
|
|
+ task.ContinueWith(t => CompleteAsync(), tco);
|
|
|
+
|
|
|
+ return subject.SubscribeAsync(observer);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return CoreAsync();
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|