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
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/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/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/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/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/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/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 a0263f3b9..72c3bcc7c 100644
--- a/src/corelib/corelib.v3.5.csproj
+++ b/src/corelib/corelib.v3.5.csproj
@@ -52,12 +52,15 @@
+
..\packages\TaskParallelLibrary.1.0.2856.0\lib\Net35\System.Threading.dll
+
+
@@ -106,11 +109,21 @@
+
+
+
+
+
+
+
+
+
+
@@ -270,6 +283,8 @@
+
+
diff --git a/src/corelib/corelib.v4.0.csproj b/src/corelib/corelib.v4.0.csproj
index e56749e91..7cd162507 100644
--- a/src/corelib/corelib.v4.0.csproj
+++ b/src/corelib/corelib.v4.0.csproj
@@ -51,9 +51,12 @@
+
+
+
@@ -96,10 +99,19 @@
+
+
+
+
+
+
+
+
+
@@ -136,6 +148,7 @@
+
@@ -260,6 +273,8 @@
+
+
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 @@
+