/// <summary> /// Attaches a continuation to the given promise and registers the resulting task for pipelining. /// </summary> /// <typeparam name="T">Task result type</typeparam> /// <param name="promise">The promise</param> /// <param name="then">The continuation</param> /// <returns>Task representing the future answer</returns> /// <exception cref="ArgumentNullException"><paramref name="promise"/> or <paramref name="then"/> is null.</exception> /// <exception cref="ArgumentException">The pomise was already registered.</exception> public static Task <T> MakePipelineAware <T>(IPromisedAnswer promise, Func <DeserializerState, T> then) { async Task <T> AwaitAnswer() { return(then(await promise.WhenReturned)); } var rtask = AwaitAnswer(); _taskTable.Add(rtask, promise); return(rtask); }
static async void DisposeCtrWhenReturned(CancellationTokenRegistration ctr, IPromisedAnswer answer) { try { await answer.WhenReturned; } catch { } finally { ctr.Dispose(); } }
/// <summary> /// Attaches a continuation to the given promise and registers the resulting task for pipelining. /// </summary> /// <typeparam name="T">Task result type</typeparam> /// <param name="promise">The promise</param> /// <param name="then">The continuation</param> /// <returns>Task representing the future answer</returns> /// <exception cref="ArgumentNullException"><paramref name="promise"/> or <paramref name="then"/> is null.</exception> /// <exception cref="ArgumentException">The pomise was already registered.</exception> public static Task <T> MakePipelineAware <T>(IPromisedAnswer promise, Func <DeserializerState, T> then) { async Task <T> AwaitAnswer() { return(then(await promise.WhenReturned)); } var rtask = AwaitAnswer(); try { // Really weird: We'd expect AwaitAnswer() to initialize a new Task instance upon each invocation. // However, this does not seem to be always true (as indicated by CI test suite). An explanation might be // that the underlying implementation recycles Task instances (um, really? doesn't make sense. But the // observation doesn't make sense, either). _taskTable.Add(rtask, promise); } catch (ArgumentException) { if (rtask.IsCompleted) { // Force .NET to create a new Task instance if (rtask.IsCanceled) { rtask = Task.FromCanceled <T>(new CancellationToken(true)); } else if (rtask.IsFaulted) { rtask = Task.FromException <T>(rtask.Exception !.InnerException !); } else { rtask = Task.FromResult <T>(rtask.Result); } _taskTable.Add(rtask, promise); } else { throw new InvalidOperationException("What the heck is wrong with Task?"); } } return(rtask); }
/// <summary> /// Attaches a continuation to the given promise and registers the resulting task for pipelining. /// </summary> /// <typeparam name="T">Task result type</typeparam> /// <param name="promise">The promise</param> /// <param name="then">The continuation</param> /// <returns>Task representing the future answer</returns> /// <exception cref="ArgumentNullException"><paramref name="promise"/> or <paramref name="then"/> is null.</exception> /// <exception cref="ArgumentException">The pomise was already registered.</exception> public static Task <T> MakePipelineAware <T>(IPromisedAnswer promise, Func <DeserializerState, T> then) { async Task <T> AwaitAnswer() { var result = await promise.WhenReturned; if (promise.IsTailCall) { throw new NoResultsException(); } return(then(result)); } var rtask = AwaitAnswer(); // Rare situation: .NET maintains a cache of some pre-computed tasks for standard results (such as (int)0, (object)null). // AwaitAnswer() might indeed have chosen a fast-path optimization, such that rtask is a cached object instead of a new instance. // Once this happens the second time, and we return the same rtask for a different promise. GetAnswer()/TryGetAnswer() may return the "wrong" // promise! Fortunately, this does not really matter, since the "wrong" promise is guaranteed to return exactly the same answer. :-) _taskTable.GetValue(rtask, _ => promise); return(rtask); }