/// <summary> /// Asynchronously writes a sequence of bytes to a stream, advances the position within the stream by the number of bytes written, /// and monitors cancellation requests. /// </summary> /// <remarks> /// <para>Use the <see cref="Stream.CanWrite"/> property to determine whether the stream instance supports writing.</para> /// <para> /// If the operation is canceled before it completes, the returned task contains the <see cref="TaskStatus.Canceled"/> /// value for the <see cref="Task.Status"/> property. /// </para> /// </remarks> /// <param name="stream">The stream to write data to.</param> /// <param name="buffer">The buffer to read the data from.</param> /// <param name="offset">The zero-based byte offset in buffer from which to begin copying bytes to the stream.</param> /// <param name="count">The maximum number of bytes to write.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param> /// <returns> /// A task that represents the asynchronous write operation. /// </returns> /// <exception cref="ArgumentNullException"> /// <para>If <paramref name="stream"/> is <see langword="null"/>.</para> /// <para>-or-</para> /// <para>If <paramref name="buffer"/> is <see langword="null"/>.</para> /// </exception> /// <exception cref="ArgumentOutOfRangeException"> /// <para>If <paramref name="offset"/> is negative.</para> /// <para>-or-</para> /// <para>If <paramref name="count"/> is negative.</para> /// </exception> /// <exception cref="ArgumentException"> /// <para>If the sum of <paramref name="offset"/> and <paramref name="count"/> is larger than the buffer length.</para> /// </exception> /// <exception cref="NotSupportedException"> /// <para>If <paramref name="stream"/> does not support writing.</para> /// </exception> /// <exception cref="ObjectDisposedException"> /// <para>If <paramref name="stream"/> has been disposed.</para> /// </exception> /// <exception cref="InvalidOperationException"> /// <para>If <paramref name="stream"/> is currently in use by a previous write operation.</para> /// </exception> public static Task WriteAsync(this Stream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken) { if (stream == null) throw new ArgumentNullException("stream"); if (cancellationToken.IsCancellationRequested) return CompletedTask.Canceled(); #if NET45PLUS // This code requires the `Stream` class provide an implementation of `WriteAsync`. The unit tests will // detect any case where this results in a stack overflow. return stream.WriteAsync(buffer, offset, count, cancellationToken); #else return Task.Factory.FromAsync(stream.BeginWrite, stream.EndWrite, buffer, offset, count, null); #endif }
/// <summary> /// Asynchronously clears all buffers for a stream and causes any buffered data to be written to the underlying device, /// and monitors cancellation requests. /// </summary> /// <remarks> /// <para>If the operation is canceled before it completes, the returned task contains the <see cref="TaskStatus.Canceled"/> /// value for the <see cref="Task.Status"/> property.</para> /// <para> /// If a derived class does not flush the buffer in its implementation of the <see cref="Stream.Flush()"/> method, /// the <see cref="FlushAsync(Stream)"/> method will not flush the buffer. /// </para> /// </remarks> /// <param name="stream">The stream to flush.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param> /// <returns>A task that represents the asynchronous flush operation.</returns> /// <exception cref="ArgumentNullException">If <paramref name="stream"/> is <see langword="null"/>.</exception> /// <exception cref="ObjectDisposedException">If <paramref name="stream"/> has been disposed.</exception> public static Task FlushAsync(this Stream stream, CancellationToken cancellationToken) { if (stream == null) throw new ArgumentNullException("stream"); if (cancellationToken.IsCancellationRequested) return CompletedTask.Canceled(); #if NET45PLUS // This code requires the `Stream` class provide an implementation of `FlushAsync`. The unit tests will // detect any case where this results in a stack overflow. return stream.FlushAsync(cancellationToken); #else return Task.Factory.StartNew(state => ((Stream)state).Flush(), stream, cancellationToken, TaskCreationOptions.None, TaskScheduler.Default); #endif }
/// <summary> /// Asynchronously reads a sequence of bytes from a stream, advances the position within the stream by the number of bytes read, /// and monitors cancellation requests. /// </summary> /// <remarks> /// <para>Use the <see cref="Stream.CanRead"/> property to determine whether the stream instance supports /// reading.</para> /// <para> /// If the operation is canceled before it completes, the returned task contains the <see cref="TaskStatus.Canceled"/> /// value for the <see cref="Task.Status"/> property. /// </para> /// </remarks> /// <param name="stream">The stream to read data from.</param> /// <param name="buffer">The buffer to write the data into.</param> /// <param name="offset">The byte offset in <paramref name="buffer"/> at which to begin writing data from the stream.</param> /// <param name="count">The maximum number of bytes to read.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param> /// <returns> /// A task that represents the asynchronous read operation. When the task completes successfully, the <see cref="Task{TResult}.Result"/> /// property contains the total number of bytes read into the buffer. The result value can be less than the number of bytes requested if /// the number of bytes currently available is less than the requested number, or it can be 0 (zero) if the end of the stream has been reached. /// </returns> /// <exception cref="ArgumentNullException"> /// <para>If <paramref name="stream"/> is <see langword="null"/>.</para> /// <para>-or-</para> /// <para>If <paramref name="buffer"/> is <see langword="null"/>.</para> /// </exception> /// <exception cref="ArgumentOutOfRangeException"> /// <para>If <paramref name="offset"/> is negative.</para> /// <para>-or-</para> /// <para>If <paramref name="count"/> is negative.</para> /// </exception> /// <exception cref="ArgumentException"> /// <para>If the sum of <paramref name="offset"/> and <paramref name="count"/> is larger than the buffer length.</para> /// </exception> /// <exception cref="NotSupportedException"> /// <para>If <paramref name="stream"/> does not support reading.</para> /// </exception> /// <exception cref="ObjectDisposedException"> /// <para>If <paramref name="stream"/> has been disposed.</para> /// </exception> /// <exception cref="InvalidOperationException"> /// <para>If <paramref name="stream"/> is currently in use by a previous read operation.</para> /// </exception> public static Task <int> ReadAsync(this Stream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken) { if (stream == null) { throw new ArgumentNullException("stream"); } if (cancellationToken.IsCancellationRequested) { return(CompletedTask.Canceled <int>()); } #if NET45PLUS // This code requires the `Stream` class provide an implementation of `ReadAsync`. The unit tests will // detect any case where this results in a stack overflow. return(stream.ReadAsync(buffer, offset, count, cancellationToken)); #else return(Task <int> .Factory.FromAsync(stream.BeginRead, stream.EndRead, buffer, offset, count, null)); #endif }
/// <summary> /// Asynchronously reads the bytes from a source stream and writes them to a destination stream, /// using a specified buffer size and cancellation token. /// </summary> /// <remarks> /// <para>If the operation is canceled before it completes, the returned task contains the <see cref="TaskStatus.Canceled"/> /// value for the <see cref="Task.Status"/> property.</para> /// <para> /// Copying begins at the current position in <paramref name="stream"/>. /// </para> /// </remarks> /// <param name="stream">The source stream.</param> /// <param name="destination">The stream to which the contents of the source stream will be copied.</param> /// <param name="bufferSize">The size, in bytes, of the buffer. This value must be greater than zero. The default size is 81920.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param> /// <returns>A task that represents the asynchronous copy operation.</returns> /// <exception cref="ArgumentNullException"> /// <para>If <paramref name="stream"/> is <see langword="null"/>.</para> /// <para>-or-</para> /// <para>If <paramref name="destination"/> is <see langword="null"/>.</para> /// </exception> /// <exception cref="ArgumentOutOfRangeException"> /// <para>If <paramref name="bufferSize"/> is negative or zero.</para> /// </exception> /// <exception cref="ObjectDisposedException"> /// <para>If <paramref name="stream"/> is disposed.</para> /// <para>-or-</para> /// <para>If <paramref name="destination"/> is disposed.</para> /// </exception> /// <exception cref="NotSupportedException"> /// <para>If <paramref name="stream"/> does not support reading.</para> /// <para>-or-</para> /// <para>If <paramref name="destination"/> does not support writing.</para> /// </exception> public static Task CopyToAsync(this Stream stream, Stream destination, int bufferSize, CancellationToken cancellationToken) { if (stream == null) { throw new ArgumentNullException("stream"); } if (destination == null) { throw new ArgumentNullException("destination"); } if (!stream.CanRead) { throw new NotSupportedException("The stream does not support reading"); } if (!destination.CanWrite) { throw new NotSupportedException("The destination does not support writing"); } if (bufferSize <= 0) { throw new ArgumentOutOfRangeException("bufferSize"); } if (cancellationToken.IsCancellationRequested) { return(CompletedTask.Canceled()); } #if NET45PLUS // This code requires the `Stream` class provide an implementation of `CopyToAsync`. The unit tests will // detect any case where this results in a stack overflow. return(stream.CopyToAsync(destination, bufferSize, cancellationToken)); #else return(CopyToAsync(stream, destination, new byte[bufferSize], cancellationToken)); #endif }
/// <summary> /// Creates a task that will complete after a time delay. /// </summary> /// <remarks> /// If the cancellation token is signaled before the specified time delay, then the task /// is completed in <see cref="TaskStatus.Canceled"/> state. Otherwise, the task is /// completed in <see cref="TaskStatus.RanToCompletion"/> state once the specified time /// delay has expired. /// <para>This method ignores any fractional milliseconds when evaluating <paramref name="delay"/>.</para> /// </remarks> /// <param name="delay">The time span to wait before completing the returned task</param> /// <param name="cancellationToken">The cancellation token that will be checked prior to completing the returned task</param> /// <returns>A task that represents the time delay</returns> /// <exception cref="ArgumentOutOfRangeException">If <paramref name="delay"/> represents a negative time interval.</exception> /// <exception cref="TaskCanceledException">If the task has been canceled.</exception> /// <exception cref="ObjectDisposedException">If the provided <paramref name="cancellationToken"/> has already been disposed.</exception> public static Task Delay(TimeSpan delay, CancellationToken cancellationToken) { #if NET45PLUS return(Task.Delay(delay, cancellationToken)); #else long totalMilliseconds = (long)delay.TotalMilliseconds; if (totalMilliseconds < 0) { throw new ArgumentOutOfRangeException("delay"); } if (cancellationToken.IsCancellationRequested) { return(CompletedTask.Canceled()); } if (totalMilliseconds == 0) { return(CompletedTask.Default); } TaskCompletionSource <VoidResult> result = new TaskCompletionSource <VoidResult>(); #if !PORTABLE TaskCompletionSource <VoidResult> intermediateResult = new TaskCompletionSource <VoidResult>(); RegisteredWaitHandle timerRegisteredWaitHandle = null; RegisteredWaitHandle cancellationRegisteredWaitHandle = null; WaitOrTimerCallback timedOutCallback = (object state, bool timedOut) => { if (timedOut) { intermediateResult.TrySetResult(default(VoidResult)); } }; IAsyncResult asyncResult = intermediateResult.Task; timerRegisteredWaitHandle = ThreadPool.RegisterWaitForSingleObject(asyncResult.AsyncWaitHandle, timedOutCallback, null, delay, true); if (cancellationToken.CanBeCanceled) { WaitOrTimerCallback cancelledCallback = (object state, bool timedOut) => { if (cancellationToken.IsCancellationRequested) { intermediateResult.TrySetCanceled(); } }; cancellationRegisteredWaitHandle = ThreadPool.RegisterWaitForSingleObject(cancellationToken.WaitHandle, cancelledCallback, null, Timeout.Infinite, true); } intermediateResult.Task .ContinueWith( _ => { if (cancellationRegisteredWaitHandle != null) { cancellationRegisteredWaitHandle.Unregister(null); } if (timerRegisteredWaitHandle != null) { timerRegisteredWaitHandle.Unregister(null); } }, TaskContinuationOptions.ExecuteSynchronously) .ContinueWith( cleanupTask => { switch (cleanupTask.Status) { case TaskStatus.RanToCompletion: result.SetFromTask(intermediateResult.Task); break; case TaskStatus.Canceled: result.SetCanceled(); break; case TaskStatus.Faulted: result.SetException(cleanupTask.Exception.InnerExceptions); break; default: throw new InvalidOperationException("Unreachable"); } }); #else // Since portable-net40 doesn't provide Task.Delay and also doesn't provide ThreadPool.RegisterWaitForSingleObject, // we need to implement this functionality using timers stored in a ConditionalWeakTable, which are associated with // the actual Task instance that gets returned by this method. CancellationTokenRegistration cancellationTokenRegistration = default(CancellationTokenRegistration); Timer timer = null; TimerCallback timerCallback = state => { result.TrySetResult(default(VoidResult)); timer.Dispose(); cancellationTokenRegistration.Dispose(); }; timer = new Timer(timerCallback, null, Timeout.Infinite, Timeout.Infinite); _delayTimers.Add(result.Task, timer); timer.Change(delay, TimeSpan.FromMilliseconds(-1)); if (cancellationToken.CanBeCanceled) { Action cancellationCallback = () => { result.TrySetCanceled(); if (timer != null) { timer.Dispose(); } cancellationTokenRegistration.Dispose(); }; cancellationTokenRegistration = cancellationToken.Register(cancellationCallback); } #endif return(result.Task); #endif }
/// <summary> /// Creates a task that will complete when all of the supplied tasks have completed. /// </summary> /// <remarks> /// If any of the supplied tasks completes in a faulted state, the returned task will also /// complete in a <see cref="TaskStatus.Faulted"/> state, where its exceptions will contain /// the aggregation of the set of unwrapped exceptions from each of the supplied tasks. /// /// <para>If none of the supplied tasks faulted but at least one of them was canceled, the /// returned task will end in the <see cref="TaskStatus.Canceled"/> state.</para> /// /// <para>If none of the tasks faulted and none of the tasks were canceled, the resulting /// task will end in the <see cref="TaskStatus.RanToCompletion"/> state. The /// <see cref="Task{TResult}.Result"/> of the returned task will be set to an array /// containing all of the results of the supplied tasks in the same order as they were /// provided (e.g. if the input tasks array contained t1, t2, t3, the output task's /// <see cref="Task{TResult}.Result"/> will return an <typeparamref name="TResult"/>[] /// where <c>arr[0] == t1.Result, arr[1] == t2.Result, and arr[2] == t3.Result</c>).</para> /// /// <para>If the supplied array/enumerable contains no tasks, the returned task will /// immediately transition to a <see cref="TaskStatus.RanToCompletion"/> state before it's /// returned to the caller. The returned <typeparamref name="TResult"/>[] will be an array /// of 0 elements.</para> /// </remarks> /// <typeparam name="TResult">The type of the completed task.</typeparam> /// <param name="tasks">The tasks to wait on for completion.</param> /// <returns>A task that represents the completion of all of the supplied tasks.</returns> /// <exception cref="ArgumentNullException">If <paramref name="tasks"/> is <see langword="null"/>.</exception> /// <exception cref="ArgumentException">If <paramref name="tasks"/> contains any <see langword="null"/> values.</exception> public static Task <TResult[]> WhenAll <TResult>(IEnumerable <Task <TResult> > tasks) { #if NET45PLUS return(Task.WhenAll(tasks)); #else if (tasks == null) { throw new ArgumentNullException("tasks"); } Task <TResult>[] tasksArray = tasks.ToArray(); if (tasksArray.Length == 0) { return(CompletedTask.FromResult(new TResult[0])); } if (tasksArray.Contains(null)) { throw new ArgumentException("tasks cannot contain any null values", "tasks"); } TaskCompletionSource <TResult[]> taskCompletionSource = new TaskCompletionSource <TResult[]>(); Action <Task <TResult>[]> continuationAction = completedTasks => { List <TResult> results = new List <TResult>(); List <Exception> exceptions = new List <Exception>(); bool canceled = false; foreach (var completedTask in completedTasks) { switch (completedTask.Status) { case TaskStatus.RanToCompletion: results.Add(completedTask.Result); break; case TaskStatus.Canceled: canceled = true; break; case TaskStatus.Faulted: exceptions.AddRange(completedTask.Exception.InnerExceptions); break; default: throw new InvalidOperationException("Unreachable"); } } if (exceptions.Count > 0) { taskCompletionSource.SetException(exceptions); } else if (canceled) { taskCompletionSource.SetCanceled(); } else { taskCompletionSource.SetResult(results.ToArray()); } }; Task.Factory.ContinueWhenAll(tasksArray, continuationAction); return(taskCompletionSource.Task); #endif }