|
///----------- ----------- ----------- ----------- ----------- -----------
/// <copyright file="BeginEndAwaitableAdapter.cs" company="Microsoft">
/// Copyright (c) Microsoft Corporation. All rights reserved.
/// </copyright>
///
/// <owner>Microsoft</owner>
/// <owner>gpaperin</owner>
///----------- ----------- ----------- ----------- ----------- -----------
using System;
using System.Diagnostics.Contracts;
using System.IO;
using System.Runtime.CompilerServices;
using System.Security;
using System.Threading;
using System.Threading.Tasks;
namespace System.Threading.Tasks {
/// <summary>
/// Provides an adapter to make Begin/End pairs awaitable.
/// In general, Task.Factory.FromAsync should be used for this purpose.
/// However, for cases where absolute minimal overhead is required, this type
/// may be used to making APM pairs awaitable while minimizing overhead.
/// (APM = Asynchronous Programming Model or the Begin/End pattern.)
/// </summary>
/// <remarks>
/// This instance may be reused repeatedly. However, it must only be used
/// by a single APM invocation at a time. It's state will automatically be reset
/// when the await completes.
/// </remarks>
/// <example>
/// Usage sample:
/// <code>
/// static async Task CopyStreamAsync(Stream source, Stream dest) {
///
/// BeginEndAwaitableAdapter adapter = new BeginEndAwaitableAdapter();
/// Byte[] buffer = new Byte[0x1000];
///
/// while (true) {
///
/// source.BeginRead(buffer, 0, buffer.Length, BeginEndAwaitableAdapter.Callback, adapter);
/// Int32 numRead = source.EndRead(await adapter);
/// if (numRead == 0)
/// break;
///
/// dest.BeginWrite(buffer, 0, numRead, BeginEndAwaitableAdapter.Callback, adapter);
/// dest.EndWrite(await adapter);
/// }
/// }
/// </code>
/// </example>
internal sealed class BeginEndAwaitableAdapter : ICriticalNotifyCompletion {
/// <summary>A sentinel marker used to communicate between OnCompleted and the APM callback
/// that the callback has already run, and thus OnCompleted needs to execute the callback.</summary>
private readonly static Action CALLBACK_RAN = () => { };
/// <summary>The IAsyncResult for the APM operation.</summary>
private IAsyncResult _asyncResult;
/// <summary>The continuation delegate provided to the awaiter.</summary>
private Action _continuation;
/// <summary>A callback to be passed as the AsyncCallback to an APM pair.
/// It expects that an BeginEndAwaitableAdapter instance was supplied to the APM Begin method as the object state.</summary>
public readonly static AsyncCallback Callback = (asyncResult) => {
Contract.Assert(asyncResult != null);
Contract.Assert(asyncResult.IsCompleted);
Contract.Assert(asyncResult.AsyncState is BeginEndAwaitableAdapter);
// Get the adapter object supplied as the "object state" to the Begin method
BeginEndAwaitableAdapter adapter = (BeginEndAwaitableAdapter) asyncResult.AsyncState;
// Store the IAsyncResult into it so that it's available to the awaiter
adapter._asyncResult = asyncResult;
// If the _continuation has already been set to the actual continuation by OnCompleted, then invoke the continuation.
// Set _continuation to the CALLBACK_RAN sentinel so that IsCompleted returns true and OnCompleted sees the sentinel
// and knows to invoke the callback.
// Due to some known incorrect implementations of IAsyncResult in the Framework where CompletedSynchronously is lazily
// set to true if it is first invoked after IsCompleted is true, we cannot rely here on CompletedSynchronously for
// synchronization between the caller and the callback, and thus do not use CompletedSynchronously at all.
Action continuation = Interlocked.Exchange(ref adapter._continuation, CALLBACK_RAN);
if (continuation != null) {
Contract.Assert(continuation != CALLBACK_RAN);
continuation();
}
};
/// <summary>Gets an awaiter.</summary>
/// <returns>Returns itself as the awaiter.</returns>
public BeginEndAwaitableAdapter GetAwaiter() {
return this;
}
/// <summary>Gets whether the awaited APM operation completed.</summary>
public bool IsCompleted {
get {
// We are completed if the callback was called and it set the continuation to the CALLBACK_RAN sentinel.
// If the operation completes asynchronously, there's still a chance we'll see CALLBACK_RAN here, in which
// case we're still good to keep running synchronously.
return (_continuation == CALLBACK_RAN);
}
}
/// <summary>Schedules the continuation to run when the operation completes.</summary>
/// <param name="continuation">The continuation.</param>
[SecurityCritical]
public void UnsafeOnCompleted(Action continuation) {
Contract.Assert(continuation != null);
OnCompleted(continuation);
}
/// <summary>Schedules the continuation to run when the operation completes.</summary>
/// <param name="continuation">The continuation.</param>
public void OnCompleted(Action continuation) {
Contract.Assert(continuation != null);
// If the continuation field is null, then set it to be the target continuation
// so that when the operation completes, it'll invoke the continuation. If it's non-null,
// it was already set to the CALLBACK_RAN-sentinel by the Callback, in which case we hit a very rare ----
// where the operation didn't complete synchronously but completed asynchronously between our
// calls to IsCompleted and OnCompleted... in that case, just schedule a task to run the continuation.
if (_continuation == CALLBACK_RAN
|| Interlocked.CompareExchange(ref _continuation, continuation, null) == CALLBACK_RAN) {
Task.Run(continuation); // must run async at this point, or else we'd risk stack diving
}
}
/// <summary>Gets the IAsyncResult for the APM operation after the operation completes, and then resets the adapter.</summary>
/// <returns>The IAsyncResult for the operation.</returns>
public IAsyncResult GetResult() {
Contract.Assert(_asyncResult != null && _asyncResult.IsCompleted);
// Get the IAsyncResult
IAsyncResult result = _asyncResult;
// Reset the adapter
_asyncResult = null;
_continuation = null;
// Return the result
return result;
}
} // class BeginEndAwaitableAdapter
} // namespace
|