Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / mscorlib / system / threading / Tasks / BeginEndAwaitableAdapter.cs
1 ///----------- ----------- ----------- ----------- ----------- -----------
2 /// <copyright file="BeginEndAwaitableAdapter.cs" company="Microsoft">
3 ///     Copyright (c) Microsoft Corporation.  All rights reserved.
4 /// </copyright>                               
5 ///
6 /// <owner>Microsoft</owner>
7 /// <owner>gpaperin</owner>
8 ///----------- ----------- ----------- ----------- ----------- -----------
9
10 using System;
11 using System.Diagnostics.Contracts;
12 using System.IO;
13 using System.Runtime.CompilerServices;
14 using System.Security;
15 using System.Threading;
16 using System.Threading.Tasks;
17
18 namespace System.Threading.Tasks {
19
20 /// <summary>
21 /// Provides an adapter to make Begin/End pairs awaitable.
22 /// In general, Task.Factory.FromAsync should be used for this purpose.
23 /// However, for cases where absolute minimal overhead is required, this type
24 /// may be used to making APM pairs awaitable while minimizing overhead.
25 /// (APM = Asynchronous Programming Model  or the Begin/End pattern.)
26 /// </summary>
27 /// <remarks>
28 /// This instance may be reused repeatedly.  However, it must only be used
29 /// by a single APM invocation at a time.  It's state will automatically be reset
30 /// when the await completes.
31 /// </remarks>
32 /// <example>
33 /// Usage sample:
34 /// <code>
35 ///     static async Task CopyStreamAsync(Stream source, Stream dest) {
36 ///     
37 ///         BeginEndAwaitableAdapter adapter = new BeginEndAwaitableAdapter();
38 ///         Byte[] buffer = new Byte[0x1000];
39 ///      
40 ///         while (true) {
41 ///     
42 ///             source.BeginRead(buffer, 0, buffer.Length, BeginEndAwaitableAdapter.Callback, adapter);
43 ///             Int32 numRead = source.EndRead(await adapter);
44 ///             if (numRead == 0)
45 ///                 break;
46 ///      
47 ///             dest.BeginWrite(buffer, 0, numRead, BeginEndAwaitableAdapter.Callback, adapter);
48 ///             dest.EndWrite(await adapter);
49 ///         }
50 ///     }
51 /// </code>
52 /// </example>
53 internal sealed class BeginEndAwaitableAdapter : ICriticalNotifyCompletion {
54
55     /// <summary>A sentinel marker used to communicate between OnCompleted and the APM callback
56     /// that the callback has already run, and thus OnCompleted needs to execute the callback.</summary>
57     private readonly static Action CALLBACK_RAN = () => { };
58
59     /// <summary>The IAsyncResult for the APM operation.</summary>
60     private IAsyncResult _asyncResult;
61
62     /// <summary>The continuation delegate provided to the awaiter.</summary>
63     private Action _continuation;
64
65
66     /// <summary>A callback to be passed as the AsyncCallback to an APM pair.
67     /// It expects that an BeginEndAwaitableAdapter instance was supplied to the APM Begin method as the object state.</summary>
68     public readonly static AsyncCallback Callback = (asyncResult) => {
69
70         Contract.Assert(asyncResult != null);
71         Contract.Assert(asyncResult.IsCompleted);
72         Contract.Assert(asyncResult.AsyncState is BeginEndAwaitableAdapter);
73
74         // Get the adapter object supplied as the "object state" to the Begin method
75         BeginEndAwaitableAdapter adapter = (BeginEndAwaitableAdapter) asyncResult.AsyncState;
76
77         // Store the IAsyncResult into it so that it's available to the awaiter
78         adapter._asyncResult = asyncResult;
79
80         // If the _continuation has already been set to the actual continuation by OnCompleted, then invoke the continuation.
81         // Set _continuation to the CALLBACK_RAN sentinel so that IsCompleted returns true and OnCompleted sees the sentinel
82         // and knows to invoke the callback.
83         // Due to some known incorrect implementations of IAsyncResult in the Framework where CompletedSynchronously is lazily
84         // set to true if it is first invoked after IsCompleted is true, we cannot rely here on CompletedSynchronously for
85         // synchronization between the caller and the callback, and thus do not use CompletedSynchronously at all.
86         Action continuation = Interlocked.Exchange(ref adapter._continuation, CALLBACK_RAN);
87         if (continuation != null) {
88         
89             Contract.Assert(continuation != CALLBACK_RAN);
90             continuation();
91         }        
92     };
93
94
95     /// <summary>Gets an awaiter.</summary>
96     /// <returns>Returns itself as the awaiter.</returns>
97     public BeginEndAwaitableAdapter GetAwaiter() {
98
99         return this;
100     }
101
102
103     /// <summary>Gets whether the awaited APM operation completed.</summary>
104     public bool IsCompleted {
105         get {
106         
107             // We are completed if the callback was called and it set the continuation to the CALLBACK_RAN sentinel.
108             // If the operation completes asynchronously, there's still a chance we'll see CALLBACK_RAN here, in which
109             // case we're still good to keep running synchronously.           
110             return (_continuation == CALLBACK_RAN);            
111         }
112     }
113
114     /// <summary>Schedules the continuation to run when the operation completes.</summary>
115     /// <param name="continuation">The continuation.</param>
116     [SecurityCritical]
117     public void UnsafeOnCompleted(Action continuation) {
118
119         Contract.Assert(continuation != null);
120         OnCompleted(continuation); 
121     }
122
123
124     /// <summary>Schedules the continuation to run when the operation completes.</summary>
125     /// <param name="continuation">The continuation.</param>
126     public void OnCompleted(Action continuation) {
127
128         Contract.Assert(continuation != null);
129
130         // If the continuation field is null, then set it to be the target continuation
131         // so that when the operation completes, it'll invoke the continuation.  If it's non-null,
132         // it was already set to the CALLBACK_RAN-sentinel by the Callback, in which case we hit a very rare ----
133         // where the operation didn't complete synchronously but completed asynchronously between our
134         // calls to IsCompleted and OnCompleted... in that case, just schedule a task to run the continuation.
135         if (_continuation == CALLBACK_RAN
136                 || Interlocked.CompareExchange(ref _continuation, continuation, null) == CALLBACK_RAN) {
137
138             Task.Run(continuation); // must run async at this point, or else we'd risk stack diving
139         }
140     }
141
142
143     /// <summary>Gets the IAsyncResult for the APM operation after the operation completes, and then resets the adapter.</summary>
144     /// <returns>The IAsyncResult for the operation.</returns>
145     public IAsyncResult GetResult() {
146
147         Contract.Assert(_asyncResult != null && _asyncResult.IsCompleted);
148
149         // Get the IAsyncResult
150         IAsyncResult result = _asyncResult;
151
152         // Reset the adapter
153         _asyncResult = null;
154         _continuation = null;
155
156         // Return the result
157         return result;
158     }
159
160 }  // class BeginEndAwaitableAdapter
161
162 }  // namespace