From 5e2b277153681948792908a8984a23eac54f81c9 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Fri, 5 Jul 2013 14:43:46 -0500 Subject: [PATCH 1/8] Add extension method WebRequest.GetResponseAsync --- src/corelib/Core/WebRequestExtensions.cs | 124 +++++++++++++++++++++++ src/corelib/corelib.v3.5.csproj | 1 + src/corelib/corelib.v4.0.csproj | 1 + src/testing/unit/AsyncWebRequestTests.cs | 96 ++++++++++++++++++ src/testing/unit/unit.csproj | 1 + 5 files changed, 223 insertions(+) create mode 100644 src/corelib/Core/WebRequestExtensions.cs create mode 100644 src/testing/unit/AsyncWebRequestTests.cs diff --git a/src/corelib/Core/WebRequestExtensions.cs b/src/corelib/Core/WebRequestExtensions.cs new file mode 100644 index 000000000..048ae0f91 --- /dev/null +++ b/src/corelib/Core/WebRequestExtensions.cs @@ -0,0 +1,124 @@ +namespace net.openstack.Core +{ + using System; + using System.Net; + using System.Threading; + using System.Threading.Tasks; + + /// + /// Provides extension methods for asynchronous operations on + /// objects. + /// + /// + /// + public static class WebRequestExtensions + { + /// + /// Returns a response to an Internet request as an asynchronous operation. + /// + /// + /// This operation will not block. The returned object will + /// complete after a response to an Internet request is available. + /// + /// The request. + /// A object which represents the asynchronous operation. + /// If is null. + public static Task GetResponseAsync(this WebRequest request) + { + if (request == null) + throw new ArgumentNullException("request"); + + return GetResponseAsync(request, CancellationToken.None); + } + + /// + /// Returns a response to an Internet request as an asynchronous operation. + /// + /// + /// This operation will not block. The returned object will + /// complete after a response to an Internet request is available. + /// + /// The request. + /// The that will be assigned to the new . + /// A object which represents the asynchronous operation. + /// If is null. + /// + /// If was previously called. + /// -or- + /// If the timeout period for the request expired. + /// -or- + /// If an error occurred while processing the request. + /// + public static Task GetResponseAsync(this WebRequest request, CancellationToken cancellationToken) + { + if (request == null) + throw new ArgumentNullException("request"); + + bool timeout = false; + TaskCompletionSource completionSource = new TaskCompletionSource(); + + RegisteredWaitHandle timerRegisteredWaitHandle = null; + RegisteredWaitHandle cancellationRegisteredWaitHandle = null; + AsyncCallback completedCallback = + result => + { + try + { + if (cancellationRegisteredWaitHandle != null) + cancellationRegisteredWaitHandle.Unregister(null); + + if (timerRegisteredWaitHandle != null) + timerRegisteredWaitHandle.Unregister(null); + + completionSource.TrySetResult(request.EndGetResponse(result)); + } + catch (WebException ex) + { + if (timeout) + completionSource.TrySetException(new WebException("No response was received during the time-out period for a request.", WebExceptionStatus.Timeout)); + else if (cancellationToken.IsCancellationRequested) + completionSource.TrySetCanceled(); + else + completionSource.TrySetException(ex); + } + catch (Exception ex) + { + completionSource.TrySetException(ex); + } + }; + + IAsyncResult asyncResult = request.BeginGetResponse(completedCallback, null); + if (!asyncResult.IsCompleted) + { + if (request.Timeout != Timeout.Infinite) + { + WaitOrTimerCallback timedOutCallback = + (object state, bool timedOut) => + { + if (timedOut) + { + timeout = true; + request.Abort(); + } + }; + + timerRegisteredWaitHandle = ThreadPool.RegisterWaitForSingleObject(asyncResult.AsyncWaitHandle, timedOutCallback, null, request.Timeout, true); + } + + if (cancellationToken.CanBeCanceled) + { + WaitOrTimerCallback cancelledCallback = + (object state, bool timedOut) => + { + if (cancellationToken.IsCancellationRequested) + request.Abort(); + }; + + cancellationRegisteredWaitHandle = ThreadPool.RegisterWaitForSingleObject(cancellationToken.WaitHandle, cancelledCallback, null, Timeout.Infinite, true); + } + } + + return completionSource.Task; + } + } +} diff --git a/src/corelib/corelib.v3.5.csproj b/src/corelib/corelib.v3.5.csproj index a0263f3b9..aaad73ccb 100644 --- a/src/corelib/corelib.v3.5.csproj +++ b/src/corelib/corelib.v3.5.csproj @@ -111,6 +111,7 @@ + diff --git a/src/corelib/corelib.v4.0.csproj b/src/corelib/corelib.v4.0.csproj index e56749e91..113f285fa 100644 --- a/src/corelib/corelib.v4.0.csproj +++ b/src/corelib/corelib.v4.0.csproj @@ -136,6 +136,7 @@ + diff --git a/src/testing/unit/AsyncWebRequestTests.cs b/src/testing/unit/AsyncWebRequestTests.cs new file mode 100644 index 000000000..b50290b34 --- /dev/null +++ b/src/testing/unit/AsyncWebRequestTests.cs @@ -0,0 +1,96 @@ +namespace OpenStackNet.Testing.Unit +{ + using Microsoft.VisualStudio.TestTools.UnitTesting; + using net.openstack.Core; + using System; + using System.Collections.ObjectModel; + using System.Net; + using System.Threading; + using System.Threading.Tasks; + + /// + [TestClass] + public class AsyncWebRequestTests + { + [TestMethod] + public void TestAsyncWebRequest() + { + Uri uri = new Uri("http://google.com"); + WebRequest request = HttpWebRequest.Create(uri); + Task response = request.GetResponseAsync(); + response.Wait(); + } + + [TestMethod] + public void TestAsyncWebRequestTimeout() + { + Uri uri = new Uri("http://google.com"); + WebRequest request = HttpWebRequest.Create(uri); + request.Timeout = 0; + Task response = request.GetResponseAsync(); + try + { + response.Wait(); + Assert.Fail("Expected an exception"); + } + catch (AggregateException exception) + { + Assert.AreEqual(TaskStatus.Faulted, response.Status); + + ReadOnlyCollection exceptions = exception.InnerExceptions; + Assert.AreEqual(1, exceptions.Count); + Assert.IsInstanceOfType(exceptions[0], typeof(WebException)); + + WebException webException = (WebException)exceptions[0]; + Assert.AreEqual(WebExceptionStatus.Timeout, webException.Status); + } + } + + [TestMethod] + public void TestAsyncWebRequestCancellation() + { + Uri uri = new Uri("http://google.com"); + WebRequest request = HttpWebRequest.Create(uri); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + Task response = request.GetResponseAsync(cancellationTokenSource.Token); + cancellationTokenSource.Cancel(); + try + { + response.Wait(); + Assert.Fail("Expected an exception"); + } + catch (AggregateException exception) + { + Assert.AreEqual(TaskStatus.Canceled, response.Status); + + ReadOnlyCollection exceptions = exception.InnerExceptions; + Assert.AreEqual(1, exceptions.Count); + Assert.IsInstanceOfType(exceptions[0], typeof(OperationCanceledException)); + } + } + + [TestMethod] + public void TestAsyncWebRequestError() + { + Uri uri = new Uri("http://google.com/fail"); + WebRequest request = HttpWebRequest.Create(uri); + Task response = request.GetResponseAsync(); + try + { + response.Wait(); + Assert.Fail("Expected an exception"); + } + catch (AggregateException exception) + { + Assert.AreEqual(TaskStatus.Faulted, response.Status); + + ReadOnlyCollection exceptions = exception.InnerExceptions; + Assert.AreEqual(1, exceptions.Count); + Assert.IsInstanceOfType(exceptions[0], typeof(WebException)); + + WebException webException = (WebException)exceptions[0]; + Assert.AreEqual(HttpStatusCode.NotFound, ((HttpWebResponse)webException.Response).StatusCode); + } + } + } +} diff --git a/src/testing/unit/unit.csproj b/src/testing/unit/unit.csproj index 8ba172d8e..550bb0332 100644 --- a/src/testing/unit/unit.csproj +++ b/src/testing/unit/unit.csproj @@ -62,6 +62,7 @@ + From a7a8cae64947c74e1c8130741e10a49010035b96 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Wed, 30 Oct 2013 10:01:34 -0500 Subject: [PATCH 2/8] Include necessary extensions from ParallelExtensionsExtras directly in the project since the ParallelExtensionsExtras package doesn't support .NET 3.5 --- .../TaskCompletionSourceExtensions.cs | 65 +++ .../TaskExtrasExtensions.cs | 526 ++++++++++++++++++ .../TaskFactoryExtensions_Common.cs | 60 ++ ...askFactoryExtensions_ContinueWhenAllAny.cs | 67 +++ .../TaskFactoryExtensions_Delayed.cs | 392 +++++++++++++ .../TaskFactoryExtensions_From.cs | 115 ++++ src/corelib/corelib.v3.5.csproj | 7 + src/corelib/corelib.v4.0.csproj | 7 + 8 files changed, 1239 insertions(+) create mode 100644 src/corelib/Core/ParallelExtensionsExtras/TaskCompletionSourceExtensions.cs create mode 100644 src/corelib/Core/ParallelExtensionsExtras/TaskExtrasExtensions.cs create mode 100644 src/corelib/Core/ParallelExtensionsExtras/TaskFactoryExtensions_Common.cs create mode 100644 src/corelib/Core/ParallelExtensionsExtras/TaskFactoryExtensions_ContinueWhenAllAny.cs create mode 100644 src/corelib/Core/ParallelExtensionsExtras/TaskFactoryExtensions_Delayed.cs create mode 100644 src/corelib/Core/ParallelExtensionsExtras/TaskFactoryExtensions_From.cs diff --git a/src/corelib/Core/ParallelExtensionsExtras/TaskCompletionSourceExtensions.cs b/src/corelib/Core/ParallelExtensionsExtras/TaskCompletionSourceExtensions.cs new file mode 100644 index 000000000..00bf7708a --- /dev/null +++ b/src/corelib/Core/ParallelExtensionsExtras/TaskCompletionSourceExtensions.cs @@ -0,0 +1,65 @@ +//-------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// File: TaskCompletionSourceExtensions.cs +// +//-------------------------------------------------------------------------- + +namespace System.Threading.Tasks +{ + /// Extension methods for TaskCompletionSource. + /// + internal static class TaskCompletionSourceExtensions + { + /// Transfers the result of a Task to the TaskCompletionSource. + /// Specifies the type of the result. + /// The TaskCompletionSource. + /// The task whose completion results should be transferred. + public static void SetFromTask(this TaskCompletionSource resultSetter, Task task) + { + switch (task.Status) + { + case TaskStatus.RanToCompletion: resultSetter.SetResult(task is Task ? ((Task)task).Result : default(TResult)); break; + case TaskStatus.Faulted: resultSetter.SetException(task.Exception.InnerExceptions); break; + case TaskStatus.Canceled: resultSetter.SetCanceled(); break; + default: throw new InvalidOperationException("The task was not completed."); + } + } + + /// Transfers the result of a Task to the TaskCompletionSource. + /// Specifies the type of the result. + /// The TaskCompletionSource. + /// The task whose completion results should be transferred. + public static void SetFromTask(this TaskCompletionSource resultSetter, Task task) + { + SetFromTask(resultSetter, (Task)task); + } + + /// Attempts to transfer the result of a Task to the TaskCompletionSource. + /// Specifies the type of the result. + /// The TaskCompletionSource. + /// The task whose completion results should be transferred. + /// Whether the transfer could be completed. + public static bool TrySetFromTask(this TaskCompletionSource resultSetter, Task task) + { + switch (task.Status) + { + case TaskStatus.RanToCompletion: return resultSetter.TrySetResult(task is Task ? ((Task)task).Result : default(TResult)); + case TaskStatus.Faulted: return resultSetter.TrySetException(task.Exception.InnerExceptions); + case TaskStatus.Canceled: return resultSetter.TrySetCanceled(); + default: throw new InvalidOperationException("The task was not completed."); + } + } + + /// Attempts to transfer the result of a Task to the TaskCompletionSource. + /// Specifies the type of the result. + /// The TaskCompletionSource. + /// The task whose completion results should be transferred. + /// Whether the transfer could be completed. + public static bool TrySetFromTask(this TaskCompletionSource resultSetter, Task task) + { + return TrySetFromTask(resultSetter, (Task)task); + } + } +} diff --git a/src/corelib/Core/ParallelExtensionsExtras/TaskExtrasExtensions.cs b/src/corelib/Core/ParallelExtensionsExtras/TaskExtrasExtensions.cs new file mode 100644 index 000000000..e825062c3 --- /dev/null +++ b/src/corelib/Core/ParallelExtensionsExtras/TaskExtrasExtensions.cs @@ -0,0 +1,526 @@ +//-------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// File: TaskExtensions.cs +// +//-------------------------------------------------------------------------- + +using System.Linq; +using System.Windows.Threading; + +namespace System.Threading.Tasks +{ + /// Extensions methods for Task. + /// + internal static class TaskExtrasExtensions + { + #region ContinueWith accepting TaskFactory + /// Creates a continuation task using the specified TaskFactory. + /// The antecedent Task. + /// The continuation action. + /// The TaskFactory. + /// A continuation task. + public static Task ContinueWith( + this Task task, Action continuationAction, TaskFactory factory) + { + return task.ContinueWith(continuationAction, factory.CancellationToken, factory.ContinuationOptions, factory.Scheduler); + } + + /// Creates a continuation task using the specified TaskFactory. + /// The antecedent Task. + /// The continuation function. + /// The TaskFactory. + /// A continuation task. + public static Task ContinueWith( + this Task task, Func continuationFunction, TaskFactory factory) + { + return task.ContinueWith(continuationFunction, factory.CancellationToken, factory.ContinuationOptions, factory.Scheduler); + } + #endregion + + #region ContinueWith accepting TaskFactory + /// Creates a continuation task using the specified TaskFactory. + /// The antecedent Task. + /// The continuation action. + /// The TaskFactory. + /// A continuation task. + public static Task ContinueWith( + this Task task, Action> continuationAction, TaskFactory factory) + { + return task.ContinueWith(continuationAction, factory.CancellationToken, factory.ContinuationOptions, factory.Scheduler); + } + + /// Creates a continuation task using the specified TaskFactory. + /// The antecedent Task. + /// The continuation function. + /// The TaskFactory. + /// A continuation task. + public static Task ContinueWith( + this Task task, Func, TNewResult> continuationFunction, TaskFactory factory) + { + return task.ContinueWith(continuationFunction, factory.CancellationToken, factory.ContinuationOptions, factory.Scheduler); + } + #endregion + + #region ToAsync(AsyncCallback, object) + /// + /// Creates a Task that represents the completion of another Task, and + /// that schedules an AsyncCallback to run upon completion. + /// + /// The antecedent Task. + /// The AsyncCallback to run. + /// The object state to use with the AsyncCallback. + /// The new task. + public static Task ToAsync(this Task task, AsyncCallback callback, object state) + { + if (task == null) throw new ArgumentNullException("task"); + + var tcs = new TaskCompletionSource(state); + task.ContinueWith(_ => + { + tcs.SetFromTask(task); + if (callback != null) callback(tcs.Task); + }); + return tcs.Task; + } + + /// + /// Creates a Task that represents the completion of another Task, and + /// that schedules an AsyncCallback to run upon completion. + /// + /// The antecedent Task. + /// The AsyncCallback to run. + /// The object state to use with the AsyncCallback. + /// The new task. + public static Task ToAsync(this Task task, AsyncCallback callback, object state) + { + if (task == null) throw new ArgumentNullException("task"); + + var tcs = new TaskCompletionSource(state); + task.ContinueWith(_ => + { + tcs.SetFromTask(task); + if (callback != null) callback(tcs.Task); + }); + return tcs.Task; + } + #endregion + + #region Exception Handling + /// Suppresses default exception handling of a Task that would otherwise re-raise the exception on the finalizer thread. + /// The Task to be monitored. + /// The original Task. + public static Task IgnoreExceptions(this Task task) + { + task.ContinueWith(t => { var ignored = t.Exception; }, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted, + TaskScheduler.Default); + return task; + } + + /// Suppresses default exception handling of a Task that would otherwise re-raise the exception on the finalizer thread. + /// The Task to be monitored. + /// The original Task. + public static Task IgnoreExceptions(this Task task) + { + return (Task)((Task)task).IgnoreExceptions(); + } + + /// Fails immediately when an exception is encountered. + /// The Task to be monitored. + /// The original Task. + public static Task FailFastOnException(this Task task) + { +#if NET35 + Action continuation = t => Environment.FailFast("A task faulted."); +#else + Action continuation = t => Environment.FailFast("A task faulted.", t.Exception); +#endif + + task.ContinueWith(continuation, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted, + TaskScheduler.Default); + return task; + } + + /// Fails immediately when an exception is encountered. + /// The Task to be monitored. + /// The original Task. + public static Task FailFastOnException(this Task task) + { + return (Task)((Task)task).FailFastOnException(); + } + + /// Propagates any exceptions that occurred on the specified task. + /// The Task whose exceptions are to be propagated. + public static void PropagateExceptions(this Task task) + { + if (!task.IsCompleted) throw new InvalidOperationException("The task has not completed."); + if (task.IsFaulted) task.Wait(); + } + + /// Propagates any exceptions that occurred on the specified tasks. + /// The Task instances whose exceptions are to be propagated. + public static void PropagateExceptions(this Task [] tasks) + { + if (tasks == null) throw new ArgumentNullException("tasks"); + if (tasks.Any(t => t == null)) throw new ArgumentException("tasks"); + if (tasks.Any(t => !t.IsCompleted)) throw new InvalidOperationException("A task has not completed."); + Task.WaitAll(tasks); + } + #endregion + +#if !NET35 + #region Observables + /// Creates an IObservable that represents the completion of a Task. + /// Specifies the type of data returned by the Task. + /// The Task to be represented as an IObservable. + /// An IObservable that represents the completion of the Task. + public static IObservable ToObservable(this Task task) + { + if (task == null) throw new ArgumentNullException("task"); + return new TaskObservable { _task = task }; + } + + /// An implementation of IObservable that wraps a Task. + /// The type of data returned by the task. + private class TaskObservable : IObservable + { + internal Task _task; + + public IDisposable Subscribe(IObserver observer) + { + // Validate arguments + if (observer == null) throw new ArgumentNullException("observer"); + + // Support cancelling the continuation if the observer is unsubscribed + var cts = new CancellationTokenSource(); + + // Create a continuation to pass data along to the observer + _task.ContinueWith(t => + { + switch (t.Status) + { + case TaskStatus.RanToCompletion: + observer.OnNext(_task.Result); + observer.OnCompleted(); + break; + + case TaskStatus.Faulted: + observer.OnError(_task.Exception); + break; + + case TaskStatus.Canceled: + observer.OnError(new TaskCanceledException(t)); + break; + } + }, cts.Token); + + // Support unsubscribe simply by canceling the continuation if it hasn't yet run + return new CancelOnDispose { Source = cts }; + } + } + + /// Translate a call to IDisposable.Dispose to a CancellationTokenSource.Cancel. + private class CancelOnDispose : IDisposable + { + internal CancellationTokenSource Source; + void IDisposable.Dispose() { Source.Cancel(); } + } + #endregion +#endif + + #region Timeouts + /// Creates a new Task that mirrors the supplied task but that will be canceled after the specified timeout. + /// The task. + /// The timeout. + /// The new Task that may time out. + public static Task WithTimeout(this Task task, TimeSpan timeout) + { + var result = new TaskCompletionSource(task.AsyncState); + var timer = new Timer(state => ((TaskCompletionSource)state).TrySetCanceled(), result, timeout, TimeSpan.FromMilliseconds(-1)); + task.ContinueWith(t => + { + timer.Dispose(); + result.TrySetFromTask(t); + }, TaskContinuationOptions.ExecuteSynchronously); + return result.Task; + } + + /// Creates a new Task that mirrors the supplied task but that will be canceled after the specified timeout. + /// Specifies the type of data contained in the task. + /// The task. + /// The timeout. + /// The new Task that may time out. + public static Task WithTimeout(this Task task, TimeSpan timeout) + { + var result = new TaskCompletionSource(task.AsyncState); + var timer = new Timer(state => ((TaskCompletionSource)state).TrySetCanceled(), result, timeout, TimeSpan.FromMilliseconds(-1)); + task.ContinueWith(t => + { + timer.Dispose(); + result.TrySetFromTask(t); + }, TaskContinuationOptions.ExecuteSynchronously); + return result.Task; + } + #endregion + + #region Children + /// + /// Ensures that a parent task can't transition into a completed state + /// until the specified task has also completed, even if it's not + /// already a child task. + /// + /// The task to attach to the current task as a child. + public static void AttachToParent(this Task task) + { + if (task == null) throw new ArgumentNullException("task"); + task.ContinueWith(t => t.Wait(), CancellationToken.None, + TaskContinuationOptions.AttachedToParent | + TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + } + #endregion + + #region Waiting + /// Waits for the task to complete execution, pumping in the meantime. + /// The task for which to wait. + /// This method is intended for usage with Windows Presentation Foundation. + public static void WaitWithPumping(this Task task) + { + if (task == null) throw new ArgumentNullException("task"); + var nestedFrame = new DispatcherFrame(); + task.ContinueWith(_ => nestedFrame.Continue = false); + Dispatcher.PushFrame(nestedFrame); + task.Wait(); + } + + /// Waits for the task to complete execution, returning the task's final status. + /// The task for which to wait. + /// The completion status of the task. + /// Unlike Wait, this method will not throw an exception if the task ends in the Faulted or Canceled state. + public static TaskStatus WaitForCompletionStatus(this Task task) + { + if (task == null) throw new ArgumentNullException("task"); + ((IAsyncResult)task).AsyncWaitHandle.WaitOne(); + return task.Status; + } + #endregion + + #region Then + /// Creates a task that represents the completion of a follow-up action when a task completes. + /// The task. + /// The action to run when the task completes. + /// The task that represents the completion of both the task and the action. + public static Task Then(this Task task, Action next) + { + if (task == null) throw new ArgumentNullException("task"); + if (next == null) throw new ArgumentNullException("next"); + + var tcs = new TaskCompletionSource(); + task.ContinueWith(delegate + { + if (task.IsFaulted) tcs.TrySetException(task.Exception.InnerExceptions); + else if (task.IsCanceled) tcs.TrySetCanceled(); + else + { + try + { + next(); + tcs.TrySetResult(null); + } + catch (Exception exc) { tcs.TrySetException(exc); } + } + }, TaskScheduler.Default); + return tcs.Task; + } + + /// Creates a task that represents the completion of a follow-up function when a task completes. + /// The task. + /// The function to run when the task completes. + /// The task that represents the completion of both the task and the function. + public static Task Then(this Task task, Func next) + { + if (task == null) throw new ArgumentNullException("task"); + if (next == null) throw new ArgumentNullException("next"); + + var tcs = new TaskCompletionSource(); + task.ContinueWith(delegate + { + if (task.IsFaulted) tcs.TrySetException(task.Exception.InnerExceptions); + else if (task.IsCanceled) tcs.TrySetCanceled(); + else + { + try + { + var result = next(); + tcs.TrySetResult(result); + } + catch (Exception exc) { tcs.TrySetException(exc); } + } + }, TaskScheduler.Default); + return tcs.Task; + } + + /// Creates a task that represents the completion of a follow-up action when a task completes. + /// The task. + /// The action to run when the task completes. + /// The task that represents the completion of both the task and the action. + public static Task Then(this Task task, Action next) + { + if (task == null) throw new ArgumentNullException("task"); + if (next == null) throw new ArgumentNullException("next"); + + var tcs = new TaskCompletionSource(); + task.ContinueWith(delegate + { + if (task.IsFaulted) tcs.TrySetException(task.Exception.InnerExceptions); + else if (task.IsCanceled) tcs.TrySetCanceled(); + else + { + try + { + next(task.Result); + tcs.TrySetResult(null); + } + catch (Exception exc) { tcs.TrySetException(exc); } + } + }, TaskScheduler.Default); + return tcs.Task; + } + + /// Creates a task that represents the completion of a follow-up function when a task completes. + /// The task. + /// The function to run when the task completes. + /// The task that represents the completion of both the task and the function. + public static Task Then(this Task task, Func next) + { + if (task == null) throw new ArgumentNullException("task"); + if (next == null) throw new ArgumentNullException("next"); + + var tcs = new TaskCompletionSource(); + task.ContinueWith(delegate + { + if (task.IsFaulted) tcs.TrySetException(task.Exception.InnerExceptions); + else if (task.IsCanceled) tcs.TrySetCanceled(); + else + { + try + { + var result = next(task.Result); + tcs.TrySetResult(result); + } + catch (Exception exc) { tcs.TrySetException(exc); } + } + }, TaskScheduler.Default); + return tcs.Task; + } + + /// Creates a task that represents the completion of a second task when a first task completes. + /// The first task. + /// The function that produces the second task. + /// The task that represents the completion of both the first and second task. + public static Task Then(this Task task, Func next) + { + if (task == null) throw new ArgumentNullException("task"); + if (next == null) throw new ArgumentNullException("next"); + + var tcs = new TaskCompletionSource(); + task.ContinueWith(delegate + { + // When the first task completes, if it faulted or was canceled, bail + if (task.IsFaulted) tcs.TrySetException(task.Exception.InnerExceptions); + else if (task.IsCanceled) tcs.TrySetCanceled(); + else + { + // Otherwise, get the next task. If it's null, bail. If not, + // when it's done we'll have our result. + try { next().ContinueWith(t => tcs.TrySetFromTask(t), TaskScheduler.Default); } + catch (Exception exc) { tcs.TrySetException(exc); } + } + }, TaskScheduler.Default); + return tcs.Task; + } + + /// Creates a task that represents the completion of a second task when a first task completes. + /// The first task. + /// The function that produces the second task based on the result of the first task. + /// The task that represents the completion of both the first and second task. + public static Task Then(this Task task, Func next) + { + if (task == null) throw new ArgumentNullException("task"); + if (next == null) throw new ArgumentNullException("next"); + + var tcs = new TaskCompletionSource(); + task.ContinueWith(delegate + { + // When the first task completes, if it faulted or was canceled, bail + if (task.IsFaulted) tcs.TrySetException(task.Exception.InnerExceptions); + else if (task.IsCanceled) tcs.TrySetCanceled(); + else + { + // Otherwise, get the next task. If it's null, bail. If not, + // when it's done we'll have our result. + try { next(task.Result).ContinueWith(t => tcs.TrySetFromTask(t), TaskScheduler.Default); } + catch (Exception exc) { tcs.TrySetException(exc); } + } + }, TaskScheduler.Default); + return tcs.Task; + } + + /// Creates a task that represents the completion of a second task when a first task completes. + /// The first task. + /// The function that produces the second task. + /// The task that represents the completion of both the first and second task. + public static Task Then(this Task task, Func> next) + { + if (task == null) throw new ArgumentNullException("task"); + if (next == null) throw new ArgumentNullException("next"); + + var tcs = new TaskCompletionSource(); + task.ContinueWith(delegate + { + // When the first task completes, if it faulted or was canceled, bail + if (task.IsFaulted) tcs.TrySetException(task.Exception.InnerExceptions); + else if (task.IsCanceled) tcs.TrySetCanceled(); + else + { + // Otherwise, get the next task. If it's null, bail. If not, + // when it's done we'll have our result. + try { next().ContinueWith(t => tcs.TrySetFromTask(t), TaskScheduler.Default); } + catch (Exception exc) { tcs.TrySetException(exc); } + } + }, TaskScheduler.Default); + return tcs.Task; + } + + /// Creates a task that represents the completion of a second task when a first task completes. + /// The first task. + /// The function that produces the second task based on the result of the first. + /// The task that represents the completion of both the first and second task. + public static Task Then(this Task task, Func> next) + { + if (task == null) throw new ArgumentNullException("task"); + if (next == null) throw new ArgumentNullException("next"); + + var tcs = new TaskCompletionSource(); + task.ContinueWith(delegate + { + // When the first task completes, if it faulted or was canceled, bail + if (task.IsFaulted) tcs.TrySetException(task.Exception.InnerExceptions); + else if (task.IsCanceled) tcs.TrySetCanceled(); + else + { + // Otherwise, get the next task. If it's null, bail. If not, + // when it's done we'll have our result. + try { next(task.Result).ContinueWith(t => tcs.TrySetFromTask(t), TaskScheduler.Default); } + catch (Exception exc) { tcs.TrySetException(exc); } + } + }, TaskScheduler.Default); + return tcs.Task; + } + #endregion + } +} \ No newline at end of file diff --git a/src/corelib/Core/ParallelExtensionsExtras/TaskFactoryExtensions_Common.cs b/src/corelib/Core/ParallelExtensionsExtras/TaskFactoryExtensions_Common.cs new file mode 100644 index 000000000..6fa103261 --- /dev/null +++ b/src/corelib/Core/ParallelExtensionsExtras/TaskFactoryExtensions_Common.cs @@ -0,0 +1,60 @@ +//-------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// File: TaskFactoryExtensions_Common.cs +// +//-------------------------------------------------------------------------- + +namespace System.Threading.Tasks +{ + /// Extensions for TaskFactory. + /// + internal static partial class TaskFactoryExtensions + { + /// Creates a generic TaskFactory from a non-generic one. + /// Specifies the type of Task results for the Tasks created by the new TaskFactory. + /// The TaskFactory to serve as a template. + /// The created TaskFactory. + public static TaskFactory ToGeneric(this TaskFactory factory) + { + return new TaskFactory( + factory.CancellationToken, factory.CreationOptions, factory.ContinuationOptions, factory.Scheduler); + } + + /// Creates a generic TaskFactory from a non-generic one. + /// Specifies the type of Task results for the Tasks created by the new TaskFactory. + /// The TaskFactory to serve as a template. + /// The created TaskFactory. + public static TaskFactory ToNonGeneric(this TaskFactory factory) + { + return new TaskFactory( + factory.CancellationToken, factory.CreationOptions, factory.ContinuationOptions, factory.Scheduler); + } + + /// Gets the TaskScheduler instance that should be used to schedule tasks. + public static TaskScheduler GetTargetScheduler(this TaskFactory factory) + { + if (factory == null) throw new ArgumentNullException("factory"); + return factory.Scheduler ?? TaskScheduler.Current; + } + + /// Gets the TaskScheduler instance that should be used to schedule tasks. + public static TaskScheduler GetTargetScheduler(this TaskFactory factory) + { + if (factory == null) throw new ArgumentNullException("factory"); + return factory.Scheduler != null ? factory.Scheduler : TaskScheduler.Current; + } + + /// Converts TaskCreationOptions into TaskContinuationOptions. + /// + /// + private static TaskContinuationOptions ContinuationOptionsFromCreationOptions(TaskCreationOptions creationOptions) + { + return (TaskContinuationOptions) + ((creationOptions & TaskCreationOptions.AttachedToParent) | + (creationOptions & TaskCreationOptions.PreferFairness) | + (creationOptions & TaskCreationOptions.LongRunning)); + } + } +} \ No newline at end of file diff --git a/src/corelib/Core/ParallelExtensionsExtras/TaskFactoryExtensions_ContinueWhenAllAny.cs b/src/corelib/Core/ParallelExtensionsExtras/TaskFactoryExtensions_ContinueWhenAllAny.cs new file mode 100644 index 000000000..23938f767 --- /dev/null +++ b/src/corelib/Core/ParallelExtensionsExtras/TaskFactoryExtensions_ContinueWhenAllAny.cs @@ -0,0 +1,67 @@ +//-------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// File: TaskFactoryExtensions_ContinueWhenAllAny.cs +// +//-------------------------------------------------------------------------- + +using System.Collections.Generic; + +namespace System.Threading.Tasks +{ + partial class TaskFactoryExtensions + { + /// + /// Creates a continuation Task that will complete upon + /// the completion of a set of provided Tasks. + /// + /// The TaskFactory to use to create the continuation task. + /// The array of tasks from which to continue. + /// A task that, when completed, will return the array of completed tasks. + public static Task WhenAll( + this TaskFactory factory, params Task[] tasks) + { + return factory.ContinueWhenAll(tasks, completedTasks => completedTasks); + } + + /// + /// Creates a continuation Task that will complete upon + /// the completion of a set of provided Tasks. + /// + /// The TaskFactory to use to create the continuation task. + /// The array of tasks from which to continue. + /// A task that, when completed, will return the array of completed tasks. + public static Task[]> WhenAll( + this TaskFactory factory, params Task[] tasks) + { + return factory.ContinueWhenAll(tasks, completedTasks => completedTasks); + } + + /// + /// Creates a continuation Task that will complete upon + /// the completion of any one of a set of provided Tasks. + /// + /// The TaskFactory to use to create the continuation task. + /// The array of tasks from which to continue. + /// A task that, when completed, will return the completed task. + public static Task WhenAny( + this TaskFactory factory, params Task[] tasks) + { + return factory.ContinueWhenAny(tasks, completedTask => completedTask); + } + + /// + /// Creates a continuation Task that will complete upon + /// the completion of any one of a set of provided Tasks. + /// + /// The TaskFactory to use to create the continuation task. + /// The array of tasks from which to continue. + /// A task that, when completed, will return the completed task. + public static Task> WhenAny( + this TaskFactory factory, params Task[] tasks) + { + return factory.ContinueWhenAny(tasks, completedTask => completedTask); + } + } +} \ No newline at end of file diff --git a/src/corelib/Core/ParallelExtensionsExtras/TaskFactoryExtensions_Delayed.cs b/src/corelib/Core/ParallelExtensionsExtras/TaskFactoryExtensions_Delayed.cs new file mode 100644 index 000000000..eb82ba8cf --- /dev/null +++ b/src/corelib/Core/ParallelExtensionsExtras/TaskFactoryExtensions_Delayed.cs @@ -0,0 +1,392 @@ +//-------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// File: TaskFactoryExtensions_Delayed.cs +// +//-------------------------------------------------------------------------- + +namespace System.Threading.Tasks +{ + partial class TaskFactoryExtensions + { + #region TaskFactory No Action + /// Creates a Task that will complete after the specified delay. + /// The TaskFactory. + /// The delay after which the Task should transition to RanToCompletion. + /// A Task that will be completed after the specified duration. + public static Task StartNewDelayed( + this TaskFactory factory, int millisecondsDelay) + { + return StartNewDelayed(factory, millisecondsDelay, CancellationToken.None); + } + + /// Creates a Task that will complete after the specified delay. + /// The TaskFactory. + /// The delay after which the Task should transition to RanToCompletion. + /// The cancellation token that can be used to cancel the timed task. + /// A Task that will be completed after the specified duration and that's cancelable with the specified token. + public static Task StartNewDelayed(this TaskFactory factory, int millisecondsDelay, CancellationToken cancellationToken) + { + // Validate arguments + if (factory == null) throw new ArgumentNullException("factory"); + if (millisecondsDelay < 0) throw new ArgumentOutOfRangeException("millisecondsDelay"); + + // Check for a pre-canceled token + if (cancellationToken.IsCancellationRequested) + return factory.FromCancellation(cancellationToken); + + // Create the timed task + var tcs = new TaskCompletionSource(factory.CreationOptions); + var ctr = default(CancellationTokenRegistration); + + // Create the timer but don't start it yet. If we start it now, + // it might fire before ctr has been set to the right registration. + var timer = new Timer(self => + { + // Clean up both the cancellation token and the timer, and try to transition to completed + ctr.Dispose(); + ((Timer)self).Dispose(); + tcs.TrySetResult(null); + }); + + // Register with the cancellation token. + if (cancellationToken.CanBeCanceled) + { + // When cancellation occurs, cancel the timer and try to transition to canceled. + // There could be a race, but it's benign. + ctr = cancellationToken.Register(() => + { + timer.Dispose(); + tcs.TrySetCanceled(); + }); + } + + // Start the timer and hand back the task... + try { timer.Change(millisecondsDelay, Timeout.Infinite); } + catch(ObjectDisposedException) {} // in case there's a race with cancellation; this is benign + + return tcs.Task; + } + #endregion + + #region TaskFactory with Action + /// Creates and schedules a task for execution after the specified time delay. + /// The factory to use to create the task. + /// The delay after which the task will be scheduled. + /// The delegate executed by the task. + /// The created Task. + public static Task StartNewDelayed( + this TaskFactory factory, + int millisecondsDelay, Action action) + { + if (factory == null) throw new ArgumentNullException("factory"); + return StartNewDelayed(factory, millisecondsDelay, action, factory.CancellationToken, factory.CreationOptions, factory.GetTargetScheduler()); + } + + /// Creates and schedules a task for execution after the specified time delay. + /// The factory to use to create the task. + /// The delay after which the task will be scheduled. + /// The delegate executed by the task. + /// Options that control the task's behavior. + /// The created Task. + public static Task StartNewDelayed( + this TaskFactory factory, + int millisecondsDelay, Action action, + TaskCreationOptions creationOptions) + { + if (factory == null) throw new ArgumentNullException("factory"); + return StartNewDelayed(factory, millisecondsDelay, action, factory.CancellationToken, creationOptions, factory.GetTargetScheduler()); + } + + /// Creates and schedules a task for execution after the specified time delay. + /// The factory to use to create the task. + /// The delay after which the task will be scheduled. + /// The delegate executed by the task. + /// The cancellation token to assign to the created Task. + /// The created Task. + public static Task StartNewDelayed( + this TaskFactory factory, + int millisecondsDelay, Action action, + CancellationToken cancellationToken) + { + if (factory == null) throw new ArgumentNullException("factory"); + return StartNewDelayed(factory, millisecondsDelay, action, cancellationToken, factory.CreationOptions, factory.GetTargetScheduler()); + } + + /// Creates and schedules a task for execution after the specified time delay. + /// The factory to use to create the task. + /// The delay after which the task will be scheduled. + /// The delegate executed by the task. + /// The cancellation token to assign to the created Task. + /// Options that control the task's behavior. + /// The scheduler to which the Task will be scheduled. + /// The created Task. + public static Task StartNewDelayed( + this TaskFactory factory, + int millisecondsDelay, Action action, + CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler) + { + if (factory == null) throw new ArgumentNullException("factory"); + if (millisecondsDelay < 0) throw new ArgumentOutOfRangeException("millisecondsDelay"); + if (action == null) throw new ArgumentNullException("action"); + if (scheduler == null) throw new ArgumentNullException("scheduler"); + + return factory + .StartNewDelayed(millisecondsDelay, cancellationToken) + .ContinueWith(_ => action(), cancellationToken, TaskContinuationOptions.OnlyOnRanToCompletion, scheduler); + } + + /// Creates and schedules a task for execution after the specified time delay. + /// The factory to use to create the task. + /// The delay after which the task will be scheduled. + /// The delegate executed by the task. + /// An object provided to the delegate. + /// The created Task. + public static Task StartNewDelayed( + this TaskFactory factory, + int millisecondsDelay, Action action, object state) + { + if (factory == null) throw new ArgumentNullException("factory"); + return StartNewDelayed(factory, millisecondsDelay, action, state, factory.CancellationToken, factory.CreationOptions, factory.GetTargetScheduler()); + } + + /// Creates and schedules a task for execution after the specified time delay. + /// The factory to use to create the task. + /// The delay after which the task will be scheduled. + /// The delegate executed by the task. + /// An object provided to the delegate. + /// Options that control the task's behavior. + /// The created Task. + public static Task StartNewDelayed( + this TaskFactory factory, + int millisecondsDelay, Action action, object state, + TaskCreationOptions creationOptions) + { + if (factory == null) throw new ArgumentNullException("factory"); + return StartNewDelayed(factory, millisecondsDelay, action, state, factory.CancellationToken, creationOptions, factory.GetTargetScheduler()); + } + + /// Creates and schedules a task for execution after the specified time delay. + /// The factory to use to create the task. + /// The delay after which the task will be scheduled. + /// The delegate executed by the task. + /// An object provided to the delegate. + /// The cancellation token to assign to the created Task. + /// The created Task. + public static Task StartNewDelayed( + this TaskFactory factory, + int millisecondsDelay, Action action, object state, + CancellationToken cancellationToken) + { + if (factory == null) throw new ArgumentNullException("factory"); + return StartNewDelayed(factory, millisecondsDelay, action, state, cancellationToken, factory.CreationOptions, factory.GetTargetScheduler()); + } + + /// Creates and schedules a task for execution after the specified time delay. + /// The factory to use to create the task. + /// The delay after which the task will be scheduled. + /// The delegate executed by the task. + /// An object provided to the delegate. + /// The cancellation token to assign to the created Task. + /// Options that control the task's behavior. + /// The scheduler to which the Task will be scheduled. + /// The created Task. + public static Task StartNewDelayed( + this TaskFactory factory, + int millisecondsDelay, Action action, object state, + CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler) + { + if (factory == null) throw new ArgumentNullException("factory"); + if (millisecondsDelay < 0) throw new ArgumentOutOfRangeException("millisecondsDelay"); + if (action == null) throw new ArgumentNullException("action"); + if (scheduler == null) throw new ArgumentNullException("scheduler"); + + // Create the task that will be returned; workaround for no ContinueWith(..., state) overload. + var result = new TaskCompletionSource(state); + + // Delay a continuation to run the action + factory + .StartNewDelayed(millisecondsDelay, cancellationToken) + .ContinueWith(t => + { + if (t.IsCanceled) result.TrySetCanceled(); + else + { + try + { + action(state); + result.TrySetResult(null); + } + catch (Exception exc) { result.TrySetException(exc); } + } + }, scheduler); + + // Return the task + return result.Task; + } + #endregion + + #region TaskFactory with Func + /// Creates and schedules a task for execution after the specified time delay. + /// The factory to use to create the task. + /// The delay after which the task will be scheduled. + /// The delegate executed by the task. + /// The created Task. + public static Task StartNewDelayed( + this TaskFactory factory, + int millisecondsDelay, Func function) + { + if (factory == null) throw new ArgumentNullException("factory"); + return StartNewDelayed(factory, millisecondsDelay, function, factory.CancellationToken, factory.CreationOptions, factory.GetTargetScheduler()); + } + + /// Creates and schedules a task for execution after the specified time delay. + /// The factory to use to create the task. + /// The delay after which the task will be scheduled. + /// The delegate executed by the task. + /// Options that control the task's behavior. + /// The created Task. + public static Task StartNewDelayed( + this TaskFactory factory, + int millisecondsDelay, Func function, + TaskCreationOptions creationOptions) + { + if (factory == null) throw new ArgumentNullException("factory"); + return StartNewDelayed(factory, millisecondsDelay, function, factory.CancellationToken, creationOptions, factory.GetTargetScheduler()); + } + + /// Creates and schedules a task for execution after the specified time delay. + /// The factory to use to create the task. + /// The delay after which the task will be scheduled. + /// The delegate executed by the task. + /// The CancellationToken to assign to the Task. + /// The created Task. + public static Task StartNewDelayed( + this TaskFactory factory, + int millisecondsDelay, Func function, + CancellationToken cancellationToken) + { + if (factory == null) throw new ArgumentNullException("factory"); + return StartNewDelayed(factory, millisecondsDelay, function, cancellationToken, factory.CreationOptions, factory.GetTargetScheduler()); + } + + /// Creates and schedules a task for execution after the specified time delay. + /// The factory to use to create the task. + /// The delay after which the task will be scheduled. + /// The delegate executed by the task. + /// The CancellationToken to assign to the Task. + /// Options that control the task's behavior. + /// The scheduler to which the Task will be scheduled. + /// The created Task. + public static Task StartNewDelayed( + this TaskFactory factory, + int millisecondsDelay, Func function, + CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler) + { + if (factory == null) throw new ArgumentNullException("factory"); + if (millisecondsDelay < 0) throw new ArgumentOutOfRangeException("millisecondsDelay"); + if (function == null) throw new ArgumentNullException("function"); + if (scheduler == null) throw new ArgumentNullException("scheduler"); + + // Create the trigger and the timer to start it + var tcs = new TaskCompletionSource(); + var timer = new Timer(obj => ((TaskCompletionSource)obj).SetResult(null), + tcs, millisecondsDelay, Timeout.Infinite); + + // Return a task that executes the function when the trigger fires + return tcs.Task.ContinueWith(_ => + { + timer.Dispose(); + return function(); + }, cancellationToken, ContinuationOptionsFromCreationOptions(creationOptions), scheduler); + } + + /// Creates and schedules a task for execution after the specified time delay. + /// The factory to use to create the task. + /// The delay after which the task will be scheduled. + /// The delegate executed by the task. + /// An object provided to the delegate. + /// The created Task. + public static Task StartNewDelayed( + this TaskFactory factory, + int millisecondsDelay, Func function, object state) + { + if (factory == null) throw new ArgumentNullException("factory"); + return StartNewDelayed(factory, millisecondsDelay, function, state, factory.CancellationToken, factory.CreationOptions, factory.GetTargetScheduler()); + } + + /// Creates and schedules a task for execution after the specified time delay. + /// The factory to use to create the task. + /// The delay after which the task will be scheduled. + /// The delegate executed by the task. + /// An object provided to the delegate. + /// The CancellationToken to assign to the Task. + /// The created Task. + public static Task StartNewDelayed( + this TaskFactory factory, + int millisecondsDelay, Func function, object state, + CancellationToken cancellationToken) + { + if (factory == null) throw new ArgumentNullException("factory"); + return StartNewDelayed(factory, millisecondsDelay, function, state, cancellationToken, factory.CreationOptions, factory.GetTargetScheduler()); + } + + /// Creates and schedules a task for execution after the specified time delay. + /// The factory to use to create the task. + /// The delay after which the task will be scheduled. + /// The delegate executed by the task. + /// An object provided to the delegate. + /// Options that control the task's behavior. + /// The created Task. + public static Task StartNewDelayed( + this TaskFactory factory, + int millisecondsDelay, Func function, object state, + TaskCreationOptions creationOptions) + { + if (factory == null) throw new ArgumentNullException("factory"); + return StartNewDelayed(factory, millisecondsDelay, function, state, factory.CancellationToken, creationOptions, factory.GetTargetScheduler()); + } + + /// Creates and schedules a task for execution after the specified time delay. + /// The factory to use to create the task. + /// The delay after which the task will be scheduled. + /// The delegate executed by the task. + /// An object provided to the delegate. + /// The CancellationToken to assign to the Task. + /// Options that control the task's behavior. + /// The scheduler to which the Task will be scheduled. + /// The created Task. + public static Task StartNewDelayed( + this TaskFactory factory, + int millisecondsDelay, Func function, object state, + CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler) + { + if (factory == null) throw new ArgumentNullException("factory"); + if (millisecondsDelay < 0) throw new ArgumentOutOfRangeException("millisecondsDelay"); + if (function == null) throw new ArgumentNullException("action"); + if (scheduler == null) throw new ArgumentNullException("scheduler"); + + // Create the task that will be returned + var result = new TaskCompletionSource(state); + Timer timer = null; + + // Create the task that will run the user's function + var functionTask = new Task(function, state, creationOptions); + + // When the function task completes, transfer the results to the returned task + functionTask.ContinueWith(t => + { + result.SetFromTask(t); + timer.Dispose(); + }, cancellationToken, ContinuationOptionsFromCreationOptions(creationOptions) | TaskContinuationOptions.ExecuteSynchronously, scheduler); + + // Start the timer for the trigger + timer = new Timer(obj => ((Task)obj).Start(scheduler), + functionTask, millisecondsDelay, Timeout.Infinite); + + return result.Task; + } + #endregion + } +} \ No newline at end of file diff --git a/src/corelib/Core/ParallelExtensionsExtras/TaskFactoryExtensions_From.cs b/src/corelib/Core/ParallelExtensionsExtras/TaskFactoryExtensions_From.cs new file mode 100644 index 000000000..c6f710f79 --- /dev/null +++ b/src/corelib/Core/ParallelExtensionsExtras/TaskFactoryExtensions_From.cs @@ -0,0 +1,115 @@ +//-------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// File: TaskFactoryExtensions_From.cs +// +//-------------------------------------------------------------------------- + +namespace System.Threading.Tasks +{ + partial class TaskFactoryExtensions + { + #region TaskFactory + /// Creates a Task that has completed in the Faulted state with the specified exception. + /// The target TaskFactory. + /// The exception with which the Task should fault. + /// The completed Task. + public static Task FromException(this TaskFactory factory, Exception exception) + { + var tcs = new TaskCompletionSource(factory.CreationOptions); + tcs.SetException(exception); + return tcs.Task; + } + + /// Creates a Task that has completed in the Faulted state with the specified exception. + /// Specifies the type of payload for the new Task. + /// The target TaskFactory. + /// The exception with which the Task should fault. + /// The completed Task. + public static Task FromException(this TaskFactory factory, Exception exception) + { + var tcs = new TaskCompletionSource(factory.CreationOptions); + tcs.SetException(exception); + return tcs.Task; + } + + /// Creates a Task that has completed in the RanToCompletion state with the specified result. + /// Specifies the type of payload for the new Task. + /// The target TaskFactory. + /// The result with which the Task should complete. + /// The completed Task. + public static Task FromResult(this TaskFactory factory, TResult result) + { + var tcs = new TaskCompletionSource(factory.CreationOptions); + tcs.SetResult(result); + return tcs.Task; + } + + /// Creates a Task that has completed in the Canceled state with the specified CancellationToken. + /// The target TaskFactory. + /// The CancellationToken with which the Task should complete. + /// The completed Task. + public static Task FromCancellation(this TaskFactory factory, CancellationToken cancellationToken) + { + if (!cancellationToken.IsCancellationRequested) throw new ArgumentOutOfRangeException("cancellationToken"); + return new Task(() => { }, cancellationToken); + } + + /// Creates a Task that has completed in the Canceled state with the specified CancellationToken. + /// Specifies the type of payload for the new Task. + /// The target TaskFactory. + /// The CancellationToken with which the Task should complete. + /// The completed Task. + public static Task FromCancellation(this TaskFactory factory, CancellationToken cancellationToken) + { + if (!cancellationToken.IsCancellationRequested) throw new ArgumentOutOfRangeException("cancellationToken"); + return new Task(DelegateCache.DefaultResult, cancellationToken); + } + + /// A cache of delegates. + /// The result type. + private class DelegateCache + { + /// Function that returns default(TResult). + internal static readonly Func DefaultResult = () => default(TResult); + } + #endregion + + #region TaskFactory + /// Creates a Task that has completed in the Faulted state with the specified exception. + /// The target TaskFactory. + /// The exception with which the Task should fault. + /// The completed Task. + public static Task FromException(this TaskFactory factory, Exception exception) + { + var tcs = new TaskCompletionSource(factory.CreationOptions); + tcs.SetException(exception); + return tcs.Task; + } + + /// Creates a Task that has completed in the RanToCompletion state with the specified result. + /// Specifies the type of payload for the new Task. + /// The target TaskFactory. + /// The result with which the Task should complete. + /// The completed Task. + public static Task FromResult(this TaskFactory factory, TResult result) + { + var tcs = new TaskCompletionSource(factory.CreationOptions); + tcs.SetResult(result); + return tcs.Task; + } + + /// Creates a Task that has completed in the Canceled state with the specified CancellationToken. + /// Specifies the type of payload for the new Task. + /// The target TaskFactory. + /// The CancellationToken with which the Task should complete. + /// The completed Task. + public static Task FromCancellation(this TaskFactory factory, CancellationToken cancellationToken) + { + if (!cancellationToken.IsCancellationRequested) throw new ArgumentOutOfRangeException("cancellationToken"); + return new Task(DelegateCache.DefaultResult, cancellationToken); + } + #endregion + } +} \ No newline at end of file diff --git a/src/corelib/corelib.v3.5.csproj b/src/corelib/corelib.v3.5.csproj index aaad73ccb..aa4424166 100644 --- a/src/corelib/corelib.v3.5.csproj +++ b/src/corelib/corelib.v3.5.csproj @@ -56,6 +56,7 @@ ..\packages\TaskParallelLibrary.1.0.2856.0\lib\Net35\System.Threading.dll + @@ -107,6 +108,12 @@ + + + + + + diff --git a/src/corelib/corelib.v4.0.csproj b/src/corelib/corelib.v4.0.csproj index 113f285fa..6de8a9aa4 100644 --- a/src/corelib/corelib.v4.0.csproj +++ b/src/corelib/corelib.v4.0.csproj @@ -52,6 +52,7 @@ + @@ -97,6 +98,12 @@ + + + + + + From a30781c617d53463a9caff6a756cfd0e65fb0fce Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Wed, 30 Oct 2013 12:18:21 -0500 Subject: [PATCH 3/8] Add InternalTaskExtensions with methods for returning completed tasks --- src/corelib/Core/InternalTaskExtensions.cs | 57 ++++++++++++++++++++++ src/corelib/corelib.v3.5.csproj | 1 + src/corelib/corelib.v4.0.csproj | 1 + 3 files changed, 59 insertions(+) create mode 100644 src/corelib/Core/InternalTaskExtensions.cs diff --git a/src/corelib/Core/InternalTaskExtensions.cs b/src/corelib/Core/InternalTaskExtensions.cs new file mode 100644 index 000000000..98468990e --- /dev/null +++ b/src/corelib/Core/InternalTaskExtensions.cs @@ -0,0 +1,57 @@ +namespace net.openstack.Core +{ + using System.Threading.Tasks; + + /// + /// Provides extension methods to and instances + /// for use within the openstack.net library. + /// + /// + /// + internal static class InternalTaskExtensions + { + /// + /// Gets a completed . + /// + /// A completed . + public static Task CompletedTask() + { + return CompletedTaskHolder.Default; + } + + /// + /// Gets a completed with the specified result. + /// + /// The task result type. + /// The result of the completed task. + /// A completed , whose property returns the specified . + public static Task CompletedTask(TResult result) + { + TaskCompletionSource completionSource = new TaskCompletionSource(); + completionSource.SetResult(result); + return completionSource.Task; + } + + private static class CompletedTaskHolder + { + public static readonly Task Default; + + static CompletedTaskHolder() + { + Default = CompletedTaskHolder.Default; + } + } + + private static class CompletedTaskHolder + { + public static readonly Task Default; + + static CompletedTaskHolder() + { + TaskCompletionSource completionSource = new TaskCompletionSource(); + completionSource.SetResult(default(T)); + Default = completionSource.Task; + } + } + } +} diff --git a/src/corelib/corelib.v3.5.csproj b/src/corelib/corelib.v3.5.csproj index aa4424166..812bae5e6 100644 --- a/src/corelib/corelib.v3.5.csproj +++ b/src/corelib/corelib.v3.5.csproj @@ -107,6 +107,7 @@ + diff --git a/src/corelib/corelib.v4.0.csproj b/src/corelib/corelib.v4.0.csproj index 6de8a9aa4..5bfd86ba1 100644 --- a/src/corelib/corelib.v4.0.csproj +++ b/src/corelib/corelib.v4.0.csproj @@ -97,6 +97,7 @@ + From 4901c3a0f29fc412b29108e6fc80f28018de5f23 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Tue, 22 Oct 2013 15:15:45 -0500 Subject: [PATCH 4/8] Add utilities for implementing asynchronous providers to ProviderBase --- .../Providers/Rackspace/ProviderBase`1.cs | 415 +++++++++++++++++- .../Rackspace/WebRequestEventArgs.cs | 44 ++ .../Rackspace/WebResponseEventArgs.cs | 44 ++ src/corelib/corelib.v3.5.csproj | 3 + src/corelib/corelib.v4.0.csproj | 3 + 5 files changed, 508 insertions(+), 1 deletion(-) create mode 100644 src/corelib/Providers/Rackspace/WebRequestEventArgs.cs create mode 100644 src/corelib/Providers/Rackspace/WebResponseEventArgs.cs diff --git a/src/corelib/Providers/Rackspace/ProviderBase`1.cs b/src/corelib/Providers/Rackspace/ProviderBase`1.cs index cbba0830b..84178d47a 100644 --- a/src/corelib/Providers/Rackspace/ProviderBase`1.cs +++ b/src/corelib/Providers/Rackspace/ProviderBase`1.cs @@ -3,6 +3,8 @@ using System.IO; using System.Linq; using System.Net; +using System.Net.Mime; +using System.Threading.Tasks; using JSIStudios.SimpleRESTServices.Client; using JSIStudios.SimpleRESTServices.Client.Json; using net.openstack.Core; @@ -11,10 +13,11 @@ using net.openstack.Core.Exceptions.Response; using net.openstack.Core.Providers; using net.openstack.Core.Validators; -using net.openstack.Providers.Rackspace.Objects; using net.openstack.Providers.Rackspace.Validators; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using CancellationToken = System.Threading.CancellationToken; +using Encoding = System.Text.Encoding; namespace net.openstack.Providers.Rackspace { @@ -86,6 +89,18 @@ protected ProviderBase(CloudIdentity defaultIdentity, string defaultRegion, IId ResponseCodeValidator = httpStatusCodeValidator ?? HttpResponseCodeValidator.Default; } + /// + /// This event is fired immediately before sending an asynchronous web request. + /// + /// + public event EventHandler BeforeAsyncWebRequest; + + /// + /// This event is fired when the result of an asynchronous web request is received. + /// + /// + public event EventHandler AfterAsyncWebResponse; + /// /// Gets or sets the maximum number of connections allowed on the /// objects used for requests. If the value is null, the connection limit value for the @@ -795,5 +810,403 @@ protected virtual void CheckIdentity(CloudIdentity identity) if (GetDefaultIdentity(identity) == null) throw new InvalidOperationException("No identity was specified for the request, and no default is available for the provider."); } + + /// + /// Creates a task continuation function responsible for creating an for use + /// in asynchronous REST API calls. The input to the continuation function is a completed task which + /// computes an for an authenticated user and a base URI for use in binding + /// the URI templates for REST API calls. The continuation function calls + /// to create and prepare the resulting . + /// + /// The to use for the request. + /// The for the target URI. + /// A collection of parameters for binding the URI template in a call to . + /// An optional transformation to apply to the bound URI for the request. If this value is null, the result of binding the with will be used as the absolute request URI. + /// A task continuation delegate which can be used to create an following the completion of a task that obtains an and the base URI for a service. + /// + /// If is null. + /// -or- + /// If is null. + /// + /// + protected Func>, HttpWebRequest> PrepareRequestAsyncFunc(HttpMethod method, UriTemplate template, IDictionary parameters, Func uriTransform = null) + { + if (template == null) + throw new ArgumentNullException("template"); + if (parameters == null) + throw new ArgumentNullException("parameters"); + + return + task => + { + Uri baseUri = task.Result.Item2; + return PrepareRequestImpl(method, task.Result.Item1, template, baseUri, parameters, uriTransform); + }; + } + + /// + /// Creates a task continuation function responsible for creating an for use + /// in asynchronous REST API calls. The input to the continuation function is a completed task which + /// computes an for an authenticated user and a base URI for use in binding + /// the URI templates for REST API calls. The continuation function calls + /// to create and prepare the resulting , and then asynchronously obtains + /// the request stream for the request and writes the specified in JSON notation. + /// + /// The to use for the request. + /// The for the target URI. + /// A collection of parameters for binding the URI template in a call to . + /// A object modeling the body of the web request. The object is serialized in JSON notation for inclusion in the request. + /// An optional transformation to apply to the bound URI for the request. If this value is null, the result of binding the with will be used as the absolute request URI. + /// A task continuation delegate which can be used to create an following the completion of a task that obtains an and the base URI for a service. + /// + /// If is null. + /// -or- + /// If is null. + /// + /// + protected Func>, Task> PrepareRequestAsyncFunc(HttpMethod method, UriTemplate template, IDictionary parameters, TBody body, Func uriTransform = null) + { + return + task => + { + Uri baseUri = task.Result.Item2; + HttpWebRequest request = PrepareRequestImpl(method, task.Result.Item1, template, baseUri, parameters, uriTransform); + + string bodyText = JsonConvert.SerializeObject(body); + byte[] encodedBody = Encoding.UTF8.GetBytes(bodyText); + request.ContentType = new ContentType() { MediaType = JsonRequestSettings.JsonContentType, CharSet = "UTF-8" }.ToString(); + request.ContentLength = encodedBody.Length; + + Task streamTask = Task.Factory.FromAsync(request.BeginGetRequestStream(null, null), request.EndGetRequestStream); + return + streamTask.ContinueWith(subTask => + { + using (Stream stream = subTask.Result) + { + stream.Write(encodedBody, 0, encodedBody.Length); + } + + return request; + }); + }; + } + + /// + /// Creates and prepares an for an asynchronous REST API call. + /// + /// + /// The base implementation sets the following properties of the web request. + /// + /// + /// + /// Property + /// Value + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// ["X-Auth-Token"] + /// + /// + /// + /// + /// + /// + /// + /// + /// 14400 seconds (4 hours) + /// + /// + /// + /// + /// + /// + /// + /// The to use for the request. + /// The to use for making an authenticated REST API call. + /// The for the target URI. + /// The base URI to use for binding the URI template. + /// A collection of parameters for binding the URI template in a call to . + /// An optional transformation to apply to the bound URI for the request. If this value is null, the result of binding the with will be used as the absolute request URI. + /// An to use for making the asynchronous REST API call. + /// + /// If is null. + /// -or- + /// If is null. + /// -or- + /// If is null. + /// -or- + /// If is null. + /// + /// If is not an absolute URI. + /// + protected virtual HttpWebRequest PrepareRequestImpl(HttpMethod method, IdentityToken identityToken, UriTemplate template, Uri baseUri, IDictionary parameters, Func uriTransform) + { + Uri boundUri = template.BindByName(baseUri, parameters); + if (uriTransform != null) + boundUri = uriTransform(boundUri); + + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(boundUri); + request.Method = method.ToString().ToUpperInvariant(); + request.Accept = JsonRequestSettings.JsonContentType; + request.Headers["X-Auth-Token"] = identityToken.Id; + request.UserAgent = UserAgentGenerator.UserAgent; + request.Timeout = (int)TimeSpan.FromSeconds(14400).TotalMilliseconds; + if (ConnectionLimit.HasValue) + request.ServicePoint.ConnectionLimit = ConnectionLimit.Value; + + return request; + } + + /// + /// Gets the base absolute URI to use for making asynchronous REST API calls to this service. + /// + /// The that the task will observe. + /// + /// A object representing the asynchronous operation. When the task + /// completes successfully, the property will contain + /// a representing the base absolute URI for the service. + /// + /// + protected virtual Task GetBaseUriAsync(CancellationToken cancellationToken) + { + throw new NotSupportedException(); + } + + /// + /// Authenticate with the identity service prior to making an asynchronous REST API call. + /// + /// The that the task will observe. + /// + /// A object representing the asynchronous operation. When the task + /// completes successfully, the property will contain + /// a tuple containing the authentication information. The first element of the tuple is + /// an for the authenticated user, and the second element is + /// a base absolute the service should use for making authenticated + /// asynchronous web requests. + /// + /// + protected virtual Task> AuthenticateServiceAsync(CancellationToken cancellationToken) + { + Task authenticate = Task.Factory.StartNew(() => IdentityProvider.GetToken(GetDefaultIdentity(null))); + + Func, Task>> getBaseUri = + task => + { + Task[] tasks = { task, GetBaseUriAsync(cancellationToken) }; + return Task.Factory.ContinueWhenAll(tasks, + ts => + { + Task first = (Task)ts[0]; + Task second = (Task)ts[1]; + return Tuple.Create(first.Result, second.Result); + }); + }; + + return authenticate.ContinueWith(getBaseUri).Unwrap(); + } + + /// + /// Invokes the event for the specified . + /// + /// The web request. + /// If is null. + /// + protected virtual void OnBeforeAsyncWebRequest(HttpWebRequest request) + { + var handler = BeforeAsyncWebRequest; + if (handler != null) + handler(this, new WebRequestEventArgs(request)); + } + + /// + /// Invokes the event for the specified . + /// + /// The web response. + /// If is null. + /// + protected virtual void OnAfterAsyncWebResponse(HttpWebResponse response) + { + if (response == null) + throw new ArgumentNullException("response"); + + var handler = AfterAsyncWebResponse; + if (handler != null) + handler(this, new WebResponseEventArgs(response)); + } + + /// + /// Gets the response from an asynchronous web request, with the body of the response (if any) returned as a string. + /// + /// The that the task will observe. + /// + /// A continuation function delegate which takes an asynchronously prepared + /// and returns the resulting body of the operation, if any, as a string. + /// + /// + protected virtual Func, Task> GetResponseAsyncFunc(CancellationToken cancellationToken) + { + Func, Task> requestResource = + task => RequestResourceImplAsync(task, cancellationToken); + + Func, Tuple> readResult = + task => ReadResultImpl(task, cancellationToken); + + Func>, string> parseResult = + task => task.Result.Item2; + + Func, Task> result = + task => + { + return task.ContinueWith(requestResource).Unwrap() + .ContinueWith(readResult) + .ContinueWith(parseResult); + }; + + return result; + } + + /// + /// Gets the response from an asynchronous web request, with the body of the response (if any) returned as an object of type . + /// + /// The type for the response object. + /// The that the task will observe. + /// + /// A continuation function delegate which parses the body of the + /// and returns an object of type , as an asynchronous operation. If + /// this value is null, the conversion will be performed by calling . + /// + /// + /// A continuation function delegate which takes an asynchronously prepared + /// and returns the resulting body of the operation, if any, as an instance of type . + /// + /// + protected virtual Func, Task> GetResponseAsyncFunc(CancellationToken cancellationToken, Func>, Task> parseResult = null) + { + Func, Task> requestResource = + task => RequestResourceImplAsync(task, cancellationToken); + + Func, Tuple> readResult = + task => ReadResultImpl(task, cancellationToken); + + if (parseResult == null) + { + parseResult = task => ParseJsonResultImplAsync(task, cancellationToken); + } + + Func, Task> result = + task => + { + return task.ContinueWith(requestResource).Unwrap() + .ContinueWith(readResult) + .ContinueWith(parseResult).Unwrap(); + }; + + return result; + } + + /// + /// This method calls and then asynchronously gets the response + /// to the web request. + /// + /// + /// This method is the first step of implementing and . + /// + /// A task which created and prepared the . + /// The that the task will observe. + /// A object representing the asynchronous operation. + /// If is null. + /// + protected virtual Task RequestResourceImplAsync(Task task, CancellationToken cancellationToken) + { + if (task == null) + throw new ArgumentNullException("task"); + + OnBeforeAsyncWebRequest(task.Result); + return task.Result.GetResponseAsync(cancellationToken); + } + + /// + /// This method reads the complete body of an asynchronous as a string. + /// + /// A object representing the asynchronous operation to get the . + /// The that the task will observe. + /// A object. The first element of the tuple contains the + /// provided by as an . + /// The second element of the tuple contains the complete body of the response as a string. + /// + /// If is null. + /// + protected virtual Tuple ReadResultImpl(Task task, CancellationToken cancellationToken) + { + if (task == null) + throw new ArgumentNullException("task"); + + HttpWebResponse response; + WebException webException = null; + if (task.IsFaulted) + { + webException = task.Exception.Flatten().InnerException as WebException; + if (webException == null) + task.PropagateExceptions(); + + response = webException.Response as HttpWebResponse; + if (response == null) + task.PropagateExceptions(); + } + else + { + response = (HttpWebResponse)task.Result; + } + + OnAfterAsyncWebResponse(response); + using (StreamReader reader = new StreamReader(response.GetResponseStream())) + { + string body = reader.ReadToEnd(); + if (task.IsFaulted) + { + if (!string.IsNullOrEmpty(body)) + { + WebExceptionStatus webExceptionStatus = webException != null ? webException.Status : WebExceptionStatus.UnknownError; + WebResponse webResponse = webException != null ? webException.Response : null; + throw new WebException(body, task.Exception, webExceptionStatus, webResponse); + } + + task.PropagateExceptions(); + } + + return Tuple.Create(response, body); + } + } + + /// + /// Provides a default object parser for which converts the + /// body of an to an object of type by calling + /// + /// + /// The type for the response object. + /// A object representing the asynchronous operation to get the . + /// The that the task will observe. + /// + /// A object representing the asynchronous operation. When the operation + /// completes successfully, the property will contain an + /// object of type representing the serialized body of the response. + /// + /// If is null. + /// + protected virtual Task ParseJsonResultImplAsync(Task> task, CancellationToken cancellationToken) + { +#if NET35 + return Task.Factory.StartNew(() => JsonConvert.DeserializeObject(task.Result.Item2)); +#else + return JsonConvert.DeserializeObjectAsync(task.Result.Item2); +#endif + } } } diff --git a/src/corelib/Providers/Rackspace/WebRequestEventArgs.cs b/src/corelib/Providers/Rackspace/WebRequestEventArgs.cs new file mode 100644 index 000000000..b6a1b9291 --- /dev/null +++ b/src/corelib/Providers/Rackspace/WebRequestEventArgs.cs @@ -0,0 +1,44 @@ +namespace net.openstack.Providers.Rackspace +{ + using System; + using System.Net; + + /// + /// This class contains data for events that happen in the context of a + /// . + /// + /// + /// + public class WebRequestEventArgs : EventArgs + { + /// + /// This is the backing field for the property. + /// + private readonly HttpWebRequest _request; + + /// + /// Initializes a new instance of the class + /// with the specified web request. + /// + /// The HTTP web request. + /// If is null. + public WebRequestEventArgs(HttpWebRequest request) + { + if (request == null) + throw new ArgumentNullException("request"); + + _request = request; + } + + /// + /// Gets the associated with the event. + /// + public HttpWebRequest Request + { + get + { + return _request; + } + } + } +} diff --git a/src/corelib/Providers/Rackspace/WebResponseEventArgs.cs b/src/corelib/Providers/Rackspace/WebResponseEventArgs.cs new file mode 100644 index 000000000..3033768e4 --- /dev/null +++ b/src/corelib/Providers/Rackspace/WebResponseEventArgs.cs @@ -0,0 +1,44 @@ +namespace net.openstack.Providers.Rackspace +{ + using System; + using System.Net; + + /// + /// This class contains data for events that happen in the context of a + /// . + /// + /// + /// + public class WebResponseEventArgs : EventArgs + { + /// + /// This is the backing field for the property. + /// + private readonly HttpWebResponse _response; + + /// + /// Initializes a new instance of the class + /// with the specified web response. + /// + /// The HTTP web response. + /// If is null. + public WebResponseEventArgs(HttpWebResponse response) + { + if (response == null) + throw new ArgumentNullException("response"); + + _response = response; + } + + /// + /// Gets the associated with the event. + /// + public HttpWebResponse Response + { + get + { + return _response; + } + } + } +} diff --git a/src/corelib/corelib.v3.5.csproj b/src/corelib/corelib.v3.5.csproj index 812bae5e6..73df1f6a3 100644 --- a/src/corelib/corelib.v3.5.csproj +++ b/src/corelib/corelib.v3.5.csproj @@ -52,6 +52,7 @@ + ..\packages\TaskParallelLibrary.1.0.2856.0\lib\Net35\System.Threading.dll @@ -279,6 +280,8 @@ + + diff --git a/src/corelib/corelib.v4.0.csproj b/src/corelib/corelib.v4.0.csproj index 5bfd86ba1..e2154f770 100644 --- a/src/corelib/corelib.v4.0.csproj +++ b/src/corelib/corelib.v4.0.csproj @@ -51,6 +51,7 @@ + @@ -269,6 +270,8 @@ + + From 5f1ab61159bf3becfa8e7c7832eb87dda067f71c Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Thu, 31 Oct 2013 12:40:02 -0500 Subject: [PATCH 5/8] Add the IProgress interface for provider interface callbacks --- src/corelib/Core/IProgress`1.cs | 22 ++++++++++++++++++++++ src/corelib/corelib.v3.5.csproj | 1 + src/corelib/corelib.v4.0.csproj | 1 + 3 files changed, 24 insertions(+) create mode 100644 src/corelib/Core/IProgress`1.cs diff --git a/src/corelib/Core/IProgress`1.cs b/src/corelib/Core/IProgress`1.cs new file mode 100644 index 000000000..f14e6090d --- /dev/null +++ b/src/corelib/Core/IProgress`1.cs @@ -0,0 +1,22 @@ +namespace net.openstack.Core +{ + /// + /// Defines a provider for progress updates. + /// + /// The type of progress update value. + /// + public interface IProgress< +#if NET35 + T +#else + in T +#endif + > + { + /// + /// Reports a progress update. + /// + /// The value of the updated progress. + void Report(T value); + } +} diff --git a/src/corelib/corelib.v3.5.csproj b/src/corelib/corelib.v3.5.csproj index 73df1f6a3..9c5a29d3c 100644 --- a/src/corelib/corelib.v3.5.csproj +++ b/src/corelib/corelib.v3.5.csproj @@ -109,6 +109,7 @@ + diff --git a/src/corelib/corelib.v4.0.csproj b/src/corelib/corelib.v4.0.csproj index e2154f770..c0df76623 100644 --- a/src/corelib/corelib.v4.0.csproj +++ b/src/corelib/corelib.v4.0.csproj @@ -99,6 +99,7 @@ + From 6186bb23fd70bb23f2f542246b02904bf1ff5d13 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Thu, 31 Oct 2013 12:44:02 -0500 Subject: [PATCH 6/8] Add the AsyncCompletionOption enumeration for specifying when tasks should be considered complete --- src/corelib/Core/AsyncCompletionOption.cs | 24 +++++++++++++++++++++++ src/corelib/corelib.v3.5.csproj | 1 + src/corelib/corelib.v4.0.csproj | 1 + 3 files changed, 26 insertions(+) create mode 100644 src/corelib/Core/AsyncCompletionOption.cs diff --git a/src/corelib/Core/AsyncCompletionOption.cs b/src/corelib/Core/AsyncCompletionOption.cs new file mode 100644 index 000000000..f864dfa9e --- /dev/null +++ b/src/corelib/Core/AsyncCompletionOption.cs @@ -0,0 +1,24 @@ +namespace net.openstack.Core +{ + using System.Threading.Tasks; + + /// + /// Specifies when a representing an asynchronous server operation + /// should be considered complete. + /// + /// + public enum AsyncCompletionOption + { + /// + /// The representing the operation is considered complete after the + /// request has been submitted to the server. + /// + RequestSubmitted, + + /// + /// The representing the operation is considered complete after the + /// server has completed processing the request. + /// + RequestCompleted, + } +} diff --git a/src/corelib/corelib.v3.5.csproj b/src/corelib/corelib.v3.5.csproj index 9c5a29d3c..e27d43481 100644 --- a/src/corelib/corelib.v3.5.csproj +++ b/src/corelib/corelib.v3.5.csproj @@ -60,6 +60,7 @@ + diff --git a/src/corelib/corelib.v4.0.csproj b/src/corelib/corelib.v4.0.csproj index c0df76623..1f44ad459 100644 --- a/src/corelib/corelib.v4.0.csproj +++ b/src/corelib/corelib.v4.0.csproj @@ -56,6 +56,7 @@ + From 4383bc836372862b9ccf49ad78811edf6e98105e Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Thu, 31 Oct 2013 13:37:04 -0500 Subject: [PATCH 7/8] Initial documentation page for asynchronous services --- .../Content/AsynchronousServices.aml | 113 ++++++++++++++++++ src/Documentation/Documentation.v3.5.shfbproj | 1 + src/Documentation/Documentation.v4.0.shfbproj | 1 + src/Documentation/OpenStackSDK.content | 1 + 4 files changed, 116 insertions(+) create mode 100644 src/Documentation/Content/AsynchronousServices.aml diff --git a/src/Documentation/Content/AsynchronousServices.aml b/src/Documentation/Content/AsynchronousServices.aml new file mode 100644 index 000000000..437df8963 --- /dev/null +++ b/src/Documentation/Content/AsynchronousServices.aml @@ -0,0 +1,113 @@ + + + + + + + The openstack.net SDK is migrating to an asynchronous service model for ongoing feature support. + This page contains information about several aspects of the asynchronous interfaces which could + result in some confusion during development. It also describes the inclusion of extension methods + that allow new product features to be used in code that is not allowed to make asynchronous API + calls. + + + +
+ Exceptions Thrown by Asynchronous Methods + + + Asynchronous methods are capable of throwing exceptions before creating a + T:System.Threading.Tasks.Task or during the asynchronous + execution of the task itself. The asynchronous service interfaces do not distinguish between these + two cases, allowing for any of the specified exceptions to be thrown in either manner. + + + + + Exceptions thrown prior to the creation of the + T:System.Threading.Tasks.Task object representing the + asynchronous operation must be caught directly by the calling code. For example, if the code + throws an T:System.ArgumentNullException in this + manner, the calling code would need to contain a + catch(ArgumentNullException) or + catch(ArgumentException) handler to handle the exception. + + + + + Exceptions thrown during the asynchronous exceution of the task are wrapped in an + T:System.AggregateException object and returned by the + P:System.Threading.Tasks.Task.Exception property. + Exceptions thrown in this manner must be handled either by a task continuation that checks the + P:System.Threading.Tasks.Task.Exception property, or + by calling M:System.Threading.Tasks.Task.Wait + or checking the P:System.Threading.Tasks.Task`1.Result + property within an exception handling block that includes a + catch(AggregateException) handler. + + + + +
+ +
+ Synchronous Extensions + + + The namespace N:net.openstack.Core.Synchronous contains extension + methods that allow methods in an asynchronous service interface to be invoked synchronously. These extension + methods are not recommended for use in new development, but are provided as a compatibility aid for projects + where external restrictions preclude the direct use of the asynchronous APIs. These extension methods perform + the following functions: + + + + + Invoke the asynchronous method, wait for the resulting + T:System.Threading.Tasks.Task to complete, and (where + applicable) return the task result. + + + + + If an exception is thrown during the asynchronous execution of the method and wrapped in an + T:System.AggregateException, the extension method unwraps the inner exception and throws + it directly, just as would occur if the underlying method were executed synchronously. + + + + + The extensions for synchronous API calls do not expose all features of the underlying asynchronous API. In + particular, the following limitations apply. + + + + + For asynchronous methods taking an T:net.openstack.Core.AsyncCompletionOption parameter to control + the behavior of the task created for asynchronous server-side operations, the synchronous extension + always passes F:net.openstack.Core.AsyncCompletionOption.RequestSubmitted for the argument. + + + + + The synchronous extensions always pass P:System.Threading.CancellationToken.None for the + T:System.Threading.CancellationToken argument, and do not support asynchronous cancellation of + the call. + + + + + The synchronous extensions do not support progress callbacks, and pass null + to APIs with an T:net.openstack.Core.IProgress`1 parameter. + + + + +
+ + + +
+
diff --git a/src/Documentation/Documentation.v3.5.shfbproj b/src/Documentation/Documentation.v3.5.shfbproj index 57346e2db..c329edac4 100644 --- a/src/Documentation/Documentation.v3.5.shfbproj +++ b/src/Documentation/Documentation.v3.5.shfbproj @@ -101,6 +101,7 @@
+ diff --git a/src/Documentation/Documentation.v4.0.shfbproj b/src/Documentation/Documentation.v4.0.shfbproj index 1909d21a4..353daf477 100644 --- a/src/Documentation/Documentation.v4.0.shfbproj +++ b/src/Documentation/Documentation.v4.0.shfbproj @@ -101,6 +101,7 @@ + diff --git a/src/Documentation/OpenStackSDK.content b/src/Documentation/OpenStackSDK.content index 5c62c6b24..87c8c042d 100644 --- a/src/Documentation/OpenStackSDK.content +++ b/src/Documentation/OpenStackSDK.content @@ -16,4 +16,5 @@ + \ No newline at end of file From c4cf1d6fdf47b32dc7e7f1a7c60c32475283922f Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Thu, 7 Nov 2013 08:47:16 -0600 Subject: [PATCH 8/8] Add namespace documentation for net.openstack.Core.Synchronous --- src/corelib/Core/Synchronous/NamespaceDoc.cs | 18 ++++++++++++++++++ src/corelib/corelib.v3.5.csproj | 1 + src/corelib/corelib.v4.0.csproj | 1 + 3 files changed, 20 insertions(+) create mode 100644 src/corelib/Core/Synchronous/NamespaceDoc.cs diff --git a/src/corelib/Core/Synchronous/NamespaceDoc.cs b/src/corelib/Core/Synchronous/NamespaceDoc.cs new file mode 100644 index 000000000..8064ce728 --- /dev/null +++ b/src/corelib/Core/Synchronous/NamespaceDoc.cs @@ -0,0 +1,18 @@ +namespace net.openstack.Core.Synchronous +{ + using System.Runtime.CompilerServices; + + /// + /// The namespace defines classes providing + /// extension methods for calling asynchronous services in a synchronous manner. + /// + /// These extension methods are not recommended for use in new development, but are + /// provided as a compatibility aid for projects where external restrictions preclude + /// the direct use of the asynchronous APIs. + /// + /// + [CompilerGenerated] + internal class NamespaceDoc + { + } +} diff --git a/src/corelib/corelib.v3.5.csproj b/src/corelib/corelib.v3.5.csproj index e27d43481..72c3bcc7c 100644 --- a/src/corelib/corelib.v3.5.csproj +++ b/src/corelib/corelib.v3.5.csproj @@ -121,6 +121,7 @@ + diff --git a/src/corelib/corelib.v4.0.csproj b/src/corelib/corelib.v4.0.csproj index 1f44ad459..7cd162507 100644 --- a/src/corelib/corelib.v4.0.csproj +++ b/src/corelib/corelib.v4.0.csproj @@ -111,6 +111,7 @@ +