1 ///----------- ----------- ----------- ----------- ----------- -----------
2 /// <copyright file="BeginEndAwaitableAdapter.cs" company="Microsoft">
3 /// Copyright (c) Microsoft Corporation. All rights reserved.
6 /// <owner>Microsoft</owner>
7 /// <owner>gpaperin</owner>
8 ///----------- ----------- ----------- ----------- ----------- -----------
11 using System.Diagnostics.Contracts;
13 using System.Runtime.CompilerServices;
14 using System.Security;
15 using System.Threading;
16 using System.Threading.Tasks;
18 namespace System.Threading.Tasks {
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.)
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.
35 /// static async Task CopyStreamAsync(Stream source, Stream dest) {
37 /// BeginEndAwaitableAdapter adapter = new BeginEndAwaitableAdapter();
38 /// Byte[] buffer = new Byte[0x1000];
42 /// source.BeginRead(buffer, 0, buffer.Length, BeginEndAwaitableAdapter.Callback, adapter);
43 /// Int32 numRead = source.EndRead(await adapter);
47 /// dest.BeginWrite(buffer, 0, numRead, BeginEndAwaitableAdapter.Callback, adapter);
48 /// dest.EndWrite(await adapter);
53 internal sealed class BeginEndAwaitableAdapter : ICriticalNotifyCompletion {
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 = () => { };
59 /// <summary>The IAsyncResult for the APM operation.</summary>
60 private IAsyncResult _asyncResult;
62 /// <summary>The continuation delegate provided to the awaiter.</summary>
63 private Action _continuation;
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) => {
70 Contract.Assert(asyncResult != null);
71 Contract.Assert(asyncResult.IsCompleted);
72 Contract.Assert(asyncResult.AsyncState is BeginEndAwaitableAdapter);
74 // Get the adapter object supplied as the "object state" to the Begin method
75 BeginEndAwaitableAdapter adapter = (BeginEndAwaitableAdapter) asyncResult.AsyncState;
77 // Store the IAsyncResult into it so that it's available to the awaiter
78 adapter._asyncResult = asyncResult;
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) {
89 Contract.Assert(continuation != CALLBACK_RAN);
95 /// <summary>Gets an awaiter.</summary>
96 /// <returns>Returns itself as the awaiter.</returns>
97 public BeginEndAwaitableAdapter GetAwaiter() {
103 /// <summary>Gets whether the awaited APM operation completed.</summary>
104 public bool IsCompleted {
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);
114 /// <summary>Schedules the continuation to run when the operation completes.</summary>
115 /// <param name="continuation">The continuation.</param>
117 public void UnsafeOnCompleted(Action continuation) {
119 Contract.Assert(continuation != null);
120 OnCompleted(continuation);
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) {
128 Contract.Assert(continuation != null);
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) {
138 Task.Run(continuation); // must run async at this point, or else we'd risk stack diving
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() {
147 Contract.Assert(_asyncResult != null && _asyncResult.IsCompleted);
149 // Get the IAsyncResult
150 IAsyncResult result = _asyncResult;
154 _continuation = null;
160 } // class BeginEndAwaitableAdapter