/// <summary> /// Reads from any of the specified channels /// </summary> /// <param name="callback">The method to call when the read completes, or null.</param> /// <param name="requests">The list of requests</param> /// <param name="timeout">The maximum time to wait for a value to read.</param> /// <param name="priority">The priority used to select a channel, if multiple channels have a value that can be read.</param> /// <param name="cancelToken">The cancellation token</param> /// <typeparam name="T">The channel data type.</typeparam> private static Task <MultisetRequest <T> > ReadOrWriteAnyAsync <T>(Action <object> callback, IEnumerable <MultisetRequest <T> > requests, TimeSpan timeout, MultiChannelPriority priority, CancellationToken cancelToken = default(CancellationToken)) { // This method could also use the untyped version, // but using the type version is faster as there are no reflection // or boxing/typecasting required var tcs = new TaskCompletionSource <MultisetRequest <T> >(); // We only accept the first offer var offer = new SingleOffer <MultisetRequest <T> >(tcs, timeout == Timeout.Infinite ? Timeout.InfiniteDateTime : DateTime.Now + timeout, cancelToken); offer.SetCommitCallback(callback); switch (priority) { case MultiChannelPriority.Fair: throw new InvalidOperationException(string.Format("Construct a {0} or {1} object to use fair multichannel operations", typeof(MultiChannelSetRead <>).Name, typeof(MultiChannelSetWrite <>).Name)); case MultiChannelPriority.Random: requests = Shuffle(requests); break; default: // Use the order the input has break; } // Keep a map of awaitable items // and register the intent to read from a channel in order var tasks = new Dictionary <Task, MultisetRequest <T> >(); foreach (var c in requests) { // Timeout is handled by offer instance if (c.IsRead) { tasks[c.ReadChannel.ReadAsync(offer)] = c; } else { tasks[c.WriteChannel.WriteAsync(c.Value, offer)] = c; } // Fast exit to avoid littering the channels if we are done if (offer.IsTaken) { break; } } offer.ProbePhaseComplete(); if (tasks.Count == 0) { tcs.TrySetException(new InvalidOperationException("List of channels was empty")); return(tcs.Task); } tasks.Keys.WhenAnyNonCancelled().ContinueWith(item => Task.Run(() => { if (item.IsCanceled) { tcs.TrySetCanceled(); return; } else if (item.IsFaulted) { tcs.TrySetException(item.Exception); return; } var n = item.Result; if (offer.AtomicIsFirst()) { // Figure out which item was found if (n.IsCanceled) { tcs.SetCanceled(); } else if (n.IsFaulted) { // Unwrap aggregate exceptions if (n.Exception is AggregateException && (n.Exception as AggregateException).Flatten().InnerExceptions.Count == 1) { tcs.SetException(n.Exception.InnerException); } else { tcs.SetException(n.Exception); } } else { var orig = tasks[n]; if (orig.IsRead) { tcs.SetResult(new MultisetRequest <T>(((Task <T>)n).Result, orig.ReadChannel, null, true)); } else { tcs.SetResult(new MultisetRequest <T>(default(T), null, orig.WriteChannel, false)); } } } })); return(tcs.Task); }
/// <summary> /// Reads or writes any of the specified requests /// </summary> /// <param name="callback">The method to call when the read completes, or null.</param> /// <param name="requests">The list of requests.</param> /// <param name="timeout">The maximum time to wait for a value to read.</param> /// <param name="priority">The priority used to select a channel, if multiple channels have a value that can be read.</param> public static Task <IMultisetRequestUntyped> ReadOrWriteAnyAsync(Action <object> callback, IEnumerable <IMultisetRequestUntyped> requests, TimeSpan timeout, MultiChannelPriority priority) { var tcs = new TaskCompletionSource <IMultisetRequestUntyped>(); // We only accept the first offer var offer = new SingleOffer <IMultisetRequestUntyped>(tcs, timeout == Timeout.Infinite ? Timeout.InfiniteDateTime : DateTime.Now + timeout); offer.SetCommitCallback(callback); switch (priority) { case MultiChannelPriority.Fair: throw new Exception(string.Format("Construct a {0} or {1} object to use fair multichannel operations", typeof(MultiChannelSetRead <>).Name, typeof(MultiChannelSetWrite <>).Name)); case MultiChannelPriority.Random: requests = MultiChannelAccess.Shuffle(requests); break; default: // Use the order the input has break; } // Keep a map of awaitable items var tasks = new Dictionary <Task, IMultisetRequestUntyped>(); // Then we register the intent to read from a channel in order foreach (var c in requests) { // Timeout is handled by offer instance if (c.IsRead) { tasks[c.Channel.ReadAsync(Timeout.Infinite, offer)] = c; } else { tasks[c.Channel.WriteAsync(c.Value, Timeout.Infinite, offer)] = c; } // Fast exit to avoid littering the channels if we are done if (offer.IsTaken) { break; } } offer.ProbePhaseComplete(); if (tasks.Count == 0) { tcs.TrySetException(new InvalidOperationException("List of channels was empty")); return(tcs.Task); } tasks.Keys.WhenAnyNonCancelled().ContinueWith(item => Task.Run(() => { if (item.IsCanceled) { tcs.TrySetCanceled(); return; } else if (item.IsFaulted) { tcs.TrySetException(item.Exception); return; } if (offer.AtomicIsFirst()) { var n = item.Result; // Figure out which item was found if (n.IsCanceled) { tcs.SetCanceled(); } else if (n.IsFaulted) { // Unwrap aggregate exceptions if (n.Exception is AggregateException && (n.Exception as AggregateException).Flatten().InnerExceptions.Count == 1) { tcs.SetException(n.Exception.InnerException); } else { tcs.SetException(n.Exception); } } else { var orig = tasks[n]; if (orig.IsRead) { orig.Value = ((Task <object>)n).Result; tcs.SetResult(orig); } else { orig.Value = null; tcs.SetResult(orig); } } } })); return(tcs.Task); }