Skip to content

Instantly share code, notes, and snippets.

@JimBobSquarePants
Created March 22, 2013 16:33
Show Gist options
  • Save JimBobSquarePants/5222741 to your computer and use it in GitHub Desktop.
Save JimBobSquarePants/5222741 to your computer and use it in GitHub Desktop.
Task Extension methods for Stackoverflow question originally from http://www.codeproject.com/Articles/504197/Await-Tasks-in-Csharp4-using-Iterators
// License: CPOL at http://www.codeproject.com/info/cpol10.aspx
using System.Collections.Generic;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
namespace System.IO
{
public static class AsyncIoExtensions
{
public static Task<Stream> GetRequestStreamAsync(this WebRequest webRequest)
{
return Task.Factory.FromAsync(
webRequest.BeginGetRequestStream,
ar => webRequest.EndGetRequestStream(ar),
null);
}
public static Task<WebResponse> GetResponseAsync(this WebRequest webRequest)
{
return Task.Factory.FromAsync(
webRequest.BeginGetResponse,
ar => webRequest.EndGetResponse(ar),
null);
}
public static Task<int> ReadAsync(this Stream input, Byte[] buffer, int offset, int count)
{
return Task.Factory.FromAsync(
input.BeginRead,
(Func<IAsyncResult, int>)input.EndRead,
buffer, offset, count,
null);
}
public static Task WriteAsync(this Stream input, Byte[] buffer, int offset, int count)
{
return Task.Factory.FromAsync(
input.BeginWrite,
input.EndWrite,
buffer, offset, count,
null);
}
public static /*async*/ Task CopyToAsync(this Stream input, Stream output, CancellationToken cancellationToken = default(CancellationToken))
{
return CopyToAsyncTasks(input, output, cancellationToken).ToTask();
}
private static IEnumerable<Task> CopyToAsyncTasks(Stream input, Stream output, CancellationToken cancellationToken)
{
byte[] buffer = new byte[0x1000]; // 4 KiB
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
var readTask = input.ReadAsync(buffer, 0, buffer.Length);
yield return readTask;
if (readTask.Result == 0) break;
cancellationToken.ThrowIfCancellationRequested();
yield return output.WriteAsync(buffer, 0, readTask.Result);
}
}
}
}
// License: CPOL at http://www.codeproject.com/info/cpol10.aspx
namespace System.Threading.Tasks
{
using System.Collections.Generic;
/// <summary>
/// Extensions related to the <see cref="Task"/> classes.
/// Supports implementing "async"-style methods in C#4 using iterators.
/// </summary>
/// <remarks>
/// I would call this TaskExtensions, except that clients must name the class to use methods like <see cref="FromResult{T}(T)"/>.
/// Based on work from Await Tasks in C#4 using Iterators by Keith L Robertson.
/// <see cref="http://www.codeproject.com/Articles/504197/Await-Tasks-in-Csharp4-using-Iterators"/>
/// </remarks>
public static class TaskEx
{
/// <summary>
/// Return a Completed <see cref="Task{TResult}"/> with a specific <see cref="Task{TResult}.Result"/> value.
/// </summary>
/// <typeparam name="TResult">
/// The result
/// </typeparam>
/// <param name="resultValue">
/// The result Value.
/// </param>
/// <returns>
/// The <see cref="Task"/>.
/// </returns>
public static Task<TResult> FromResult<TResult>(TResult resultValue)
{
var completionSource = new TaskCompletionSource<TResult>();
completionSource.SetResult(resultValue);
return completionSource.Task;
}
/// <summary>
/// Transform an enumeration of <see cref="Task"/> into a single non-Result <see cref="Task"/>.
/// </summary>
/// <param name="tasks">
/// The tasks.
/// </param>
/// <returns>
/// The <see cref="Task"/>.
/// </returns>
public static Task ToTask(this IEnumerable<Task> tasks)
{
return ToTask<VoidResult>(tasks);
}
/// <summary>
/// Transform an enumeration of <see cref="Task"/> into a single <see cref="Task{TResult}"/>.
/// The final <see cref="Task"/> in <paramref name="tasks"/> must be a <see cref="Task{TResult}"/>.
/// </summary>
/// <typeparam name="TResult">
/// The task results
/// </typeparam>
/// <param name="tasks">
/// The tasks.
/// </param>
/// <returns>
/// The <see cref="Task"/>.
/// </returns>
public static Task<TResult> ToTask<TResult>(this IEnumerable<Task> tasks)
{
var taskScheduler =
SynchronizationContext.Current == null
? TaskScheduler.Default : TaskScheduler.FromCurrentSynchronizationContext();
var taskEnumerator = tasks.GetEnumerator();
var completionSource = new TaskCompletionSource<TResult>();
// Clean up the enumerator when the task completes.
completionSource.Task.ContinueWith(t => taskEnumerator.Dispose(), taskScheduler);
ToTaskDoOneStep(taskEnumerator, taskScheduler, completionSource, null);
return completionSource.Task;
}
/// <summary>
/// If the previous task Canceled or Faulted, complete the master task with the same <see cref="Task.Status"/>.
/// Obtain the next <see cref="Task"/> from the <paramref name="taskEnumerator"/>.
/// If none, complete the master task, possibly with the <see cref="Task{T}.Result"/> of the last task.
/// Otherwise, set up the task with a continuation to come do this again when it completes.
/// </summary>
private static void ToTaskDoOneStep<TResult>(
IEnumerator<Task> taskEnumerator, TaskScheduler taskScheduler,
TaskCompletionSource<TResult> completionSource, Task completedTask)
{
// Check status of previous nested task (if any), and stop if Canceled or Faulted.
TaskStatus status;
if (completedTask == null)
{
// This is the first task from the iterator; skip status check.
}
else if ((status = completedTask.Status) == TaskStatus.Canceled)
{
completionSource.SetCanceled();
return;
}
else if (status == TaskStatus.Faulted)
{
completionSource.SetException(completedTask.Exception);
return;
}
// Check for cancellation before looking for the next task.
// This causes a problem where the ultimate Task does not complete and fire any continuations; I don't know why.
// So cancellation from the Token must be handled within the iterator itself.
//if (cancellationToken.IsCancellationRequested) {
// completionSource.SetCanceled();
// return;
//}
// Find the next Task in the iterator; handle cancellation and other exceptions.
Boolean haveMore;
try
{
haveMore = taskEnumerator.MoveNext();
}
catch (OperationCanceledException cancExc)
{
//if (cancExc.CancellationToken == cancellationToken) completionSource.SetCanceled();
//else completionSource.SetException(cancExc);
completionSource.SetCanceled();
return;
}
catch (Exception exc)
{
completionSource.SetException(exc);
return;
}
if (!haveMore)
{
// No more tasks; set the result from the last completed task (if any, unless no result is requested).
// We know it's not Canceled or Faulted because we checked at the start of this method.
if (typeof(TResult) == typeof(VoidResult))
{ // No result
completionSource.SetResult(default(TResult));
}
else if (!(completedTask is Task<TResult>))
{ // Wrong result
completionSource.SetException(new InvalidOperationException(
"Asynchronous iterator " + taskEnumerator +
" requires a final result task of type " + typeof(Task<TResult>).FullName +
(completedTask == null ? ", but none was provided." :
"; the actual task type was " + completedTask.GetType().FullName)));
}
else
{
completionSource.SetResult(((Task<TResult>)completedTask).Result);
}
}
else
{
// When the nested task completes, continue by performing this function again.
// Note: This is NOT a recursive call; the current method activation will complete
// almost immediately and independently of the lambda continuation.
taskEnumerator.Current.ContinueWith(
nextTask => ToTaskDoOneStep(taskEnumerator, taskScheduler, completionSource, nextTask),
taskScheduler);
}
}
/// <summary>
/// Internal marker type for using <see cref="ToTask{T}"/> to implement <see cref="ToTask"/>.
/// </summary>
private abstract class VoidResult
{
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment