Merge pull request #3389 from lambdageek/bug-43099
[mono.git] / mcs / class / referencesource / System.Web / TaskAsyncHelper.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="TaskAsyncHelper.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>                                                                
5 //------------------------------------------------------------------------------
6
7 /*
8  * Assists in converting an method written using the Task Asynchronous Pattern to a Begin/End method pair.
9  * 
10  * Copyright (c) 2010 Microsoft Corporation
11  */
12
13 namespace System.Web {
14     using System;
15     using System.Threading.Tasks;
16
17     internal static class TaskAsyncHelper {
18
19         private static readonly Task s_completedTask = Task.FromResult<object>(null);
20
21         internal static IAsyncResult BeginTask(Func<Task> taskFunc, AsyncCallback callback, object state) {
22             Task task = taskFunc();
23             if (task == null) {
24                 // Something went wrong - let our caller handle it.
25                 return null;
26             }
27
28             // We need to wrap the inner Task so that the IAsyncResult exposed by this method
29             // has the state object that was provided as a parameter. We could be a bit smarter
30             // about this to save an allocation if the state objects are equal, but that's a
31             // micro-optimization.
32             TaskWrapperAsyncResult resultToReturn = new TaskWrapperAsyncResult(task, state);
33
34             // Task instances are always marked CompletedSynchronously = false, even if the
35             // operation completed synchronously. We should detect this and modify the IAsyncResult
36             // we pass back to our caller as appropriate. Only read the 'IsCompleted' property once
37             // to avoid a race condition where the underlying Task completes during this method.
38             bool actuallyCompletedSynchronously = task.IsCompleted;
39             if (actuallyCompletedSynchronously) {
40                 resultToReturn.ForceCompletedSynchronously();
41             }
42
43             if (callback != null) {
44                 // ContinueWith() is a bit slow: it captures execution context and hops threads. We should
45                 // avoid calling it and just invoke the callback directly if the underlying Task is
46                 // already completed. Only use ContinueWith as a fallback. There's technically a ---- here
47                 // in that the Task may have completed between the check above and the call to
48                 // ContinueWith below, but ContinueWith will do the right thing in both cases.
49                 if (actuallyCompletedSynchronously) {
50                     callback(resultToReturn);
51                 }
52                 else {
53                     task.ContinueWith(_ => callback(resultToReturn));
54                 }
55             }
56
57             return resultToReturn;
58         }
59
60         // The parameter is named 'ar' since it matches the parameter name on the EndEventHandler delegate type,
61         // and we expect that most consumers will end up invoking this method via an instance of that delegate.
62         internal static void EndTask(IAsyncResult ar) {
63             if (ar == null) {
64                 throw new ArgumentNullException("ar");
65             }
66
67             // Make sure the incoming parameter is actually the correct type.
68             TaskWrapperAsyncResult taskWrapper = ar as TaskWrapperAsyncResult;
69             if (taskWrapper == null) {
70                 // extraction failed
71                 throw new ArgumentException(SR.GetString(SR.TaskAsyncHelper_ParameterInvalid), "ar");
72             }
73
74             // The End* method doesn't actually perform any actual work, but we do need to maintain two invariants:
75             // 1. Make sure the underlying Task actually *is* complete.
76             // 2. If the Task encountered an exception, observe it here.
77             // (TaskAwaiter.GetResult() handles both of those, and it rethrows the original exception rather than an AggregateException.)
78             taskWrapper.Task.GetAwaiter().GetResult();
79         }
80
81         internal static Task CompletedTask {
82             get {
83                 return s_completedTask;
84             }
85         }
86     }
87 }