530 lines
21 KiB
C#
530 lines
21 KiB
C#
|
//------------------------------------------------------------
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
//------------------------------------------------------------
|
||
|
|
||
|
namespace System.ServiceModel.Channels
|
||
|
{
|
||
|
using System.Runtime;
|
||
|
using System.Runtime.InteropServices;
|
||
|
using System.Security;
|
||
|
using System.Security.Permissions;
|
||
|
using System.Threading;
|
||
|
|
||
|
delegate void OverlappedIOCompleteCallback(bool haveResult, int error, int bytesRead);
|
||
|
|
||
|
unsafe class OverlappedContext
|
||
|
{
|
||
|
const int HandleOffsetFromOverlapped32 = -4;
|
||
|
const int HandleOffsetFromOverlapped64 = -3;
|
||
|
|
||
|
static IOCompletionCallback completeCallback;
|
||
|
static WaitOrTimerCallback eventCallback;
|
||
|
static WaitOrTimerCallback cleanupCallback;
|
||
|
static byte[] dummyBuffer = new byte[0];
|
||
|
|
||
|
object[] bufferHolder;
|
||
|
byte* bufferPtr;
|
||
|
NativeOverlapped* nativeOverlapped;
|
||
|
GCHandle pinnedHandle;
|
||
|
object pinnedTarget;
|
||
|
Overlapped overlapped;
|
||
|
RootedHolder rootedHolder;
|
||
|
OverlappedIOCompleteCallback pendingCallback; // Null when no async I/O is pending.
|
||
|
bool deferredFree;
|
||
|
bool syncOperationPending;
|
||
|
ManualResetEvent completionEvent;
|
||
|
IntPtr eventHandle;
|
||
|
|
||
|
// Only used by unbound I/O.
|
||
|
RegisteredWaitHandle registration;
|
||
|
|
||
|
#if DEBUG_EXPENSIVE
|
||
|
StackTrace freeStack;
|
||
|
#endif
|
||
|
|
||
|
|
||
|
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
|
||
|
public OverlappedContext()
|
||
|
{
|
||
|
if (OverlappedContext.completeCallback == null)
|
||
|
{
|
||
|
OverlappedContext.completeCallback = Fx.ThunkCallback(new IOCompletionCallback(CompleteCallback));
|
||
|
}
|
||
|
if (OverlappedContext.eventCallback == null)
|
||
|
{
|
||
|
OverlappedContext.eventCallback = Fx.ThunkCallback(new WaitOrTimerCallback(EventCallback));
|
||
|
}
|
||
|
if (OverlappedContext.cleanupCallback == null)
|
||
|
{
|
||
|
OverlappedContext.cleanupCallback = Fx.ThunkCallback(new WaitOrTimerCallback(CleanupCallback));
|
||
|
}
|
||
|
|
||
|
this.bufferHolder = new object[] { OverlappedContext.dummyBuffer };
|
||
|
this.overlapped = new Overlapped();
|
||
|
this.nativeOverlapped = this.overlapped.UnsafePack(OverlappedContext.completeCallback, this.bufferHolder);
|
||
|
|
||
|
// When replacing the buffer, we need to provoke the CLR to fix up the handle of the pin.
|
||
|
this.pinnedHandle = GCHandle.FromIntPtr(*((IntPtr*)nativeOverlapped +
|
||
|
(IntPtr.Size == 4 ? HandleOffsetFromOverlapped32 : HandleOffsetFromOverlapped64)));
|
||
|
this.pinnedTarget = this.pinnedHandle.Target;
|
||
|
|
||
|
// Create the permanently rooted holder and put it in the Overlapped.
|
||
|
this.rootedHolder = new RootedHolder();
|
||
|
this.overlapped.AsyncResult = rootedHolder;
|
||
|
}
|
||
|
|
||
|
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
|
||
|
~OverlappedContext()
|
||
|
{
|
||
|
if (this.nativeOverlapped != null && !AppDomain.CurrentDomain.IsFinalizingForUnload() && !Environment.HasShutdownStarted)
|
||
|
{
|
||
|
if (this.syncOperationPending)
|
||
|
{
|
||
|
Fx.Assert(this.rootedHolder != null, "rootedHolder null in Finalize.");
|
||
|
Fx.Assert(this.rootedHolder.EventHolder != null, "rootedHolder.EventHolder null in Finalize.");
|
||
|
Fx.Assert(OverlappedContext.cleanupCallback != null, "cleanupCallback null in Finalize.");
|
||
|
|
||
|
// Can't free the overlapped. Register a callback to deal with this.
|
||
|
// This will ressurect the OverlappedContext.
|
||
|
// The completionEvent will still be alive (not finalized) since it's rooted by the pending Overlapped in the holder.
|
||
|
// We own it now and will close it in the callback.
|
||
|
ThreadPool.UnsafeRegisterWaitForSingleObject(this.rootedHolder.EventHolder, OverlappedContext.cleanupCallback, this, Timeout.Infinite, true);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Overlapped.Free(this.nativeOverlapped);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// None of the OverlappedContext methods are threadsafe.
|
||
|
// Free or FreeOrDefer can only be called once. FreeIfDeferred can be called any number of times, as long as it's only
|
||
|
// called once after FreeOrDefer.
|
||
|
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
|
||
|
public void Free()
|
||
|
{
|
||
|
if (this.pendingCallback != null)
|
||
|
{
|
||
|
throw Fx.AssertAndThrow("OverlappedContext.Free called while async operation is pending.");
|
||
|
}
|
||
|
if (this.syncOperationPending)
|
||
|
{
|
||
|
throw Fx.AssertAndThrow("OverlappedContext.Free called while [....] operation is pending.");
|
||
|
}
|
||
|
if (this.nativeOverlapped == null)
|
||
|
{
|
||
|
throw Fx.AssertAndThrow("OverlappedContext.Free called multiple times.");
|
||
|
}
|
||
|
|
||
|
#if DEBUG_EXPENSIVE
|
||
|
this.freeStack = new StackTrace();
|
||
|
#endif
|
||
|
|
||
|
// The OverlappedData is cached and reused. It looks weird if there's still a reference to it form here.
|
||
|
this.pinnedTarget = null;
|
||
|
|
||
|
NativeOverlapped* nativePtr = this.nativeOverlapped;
|
||
|
this.nativeOverlapped = null;
|
||
|
Overlapped.Free(nativePtr);
|
||
|
|
||
|
if (this.completionEvent != null)
|
||
|
{
|
||
|
this.completionEvent.Close();
|
||
|
}
|
||
|
|
||
|
GC.SuppressFinalize(this);
|
||
|
}
|
||
|
|
||
|
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
|
||
|
public bool FreeOrDefer()
|
||
|
{
|
||
|
if (this.pendingCallback != null || this.syncOperationPending)
|
||
|
{
|
||
|
this.deferredFree = true;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Free();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
|
||
|
public bool FreeIfDeferred()
|
||
|
{
|
||
|
if (this.deferredFree)
|
||
|
{
|
||
|
return FreeOrDefer();
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
|
||
|
public void StartAsyncOperation(byte[] buffer, OverlappedIOCompleteCallback callback, bool bound)
|
||
|
{
|
||
|
if (callback == null)
|
||
|
{
|
||
|
throw Fx.AssertAndThrow("StartAsyncOperation called with null callback.");
|
||
|
}
|
||
|
if (this.pendingCallback != null)
|
||
|
{
|
||
|
throw Fx.AssertAndThrow("StartAsyncOperation called while another is in progress.");
|
||
|
}
|
||
|
if (this.syncOperationPending)
|
||
|
{
|
||
|
throw Fx.AssertAndThrow("StartAsyncOperation called while a [....] operation was already pending.");
|
||
|
}
|
||
|
if (this.nativeOverlapped == null)
|
||
|
{
|
||
|
throw Fx.AssertAndThrow("StartAsyncOperation called on freed OverlappedContext.");
|
||
|
}
|
||
|
|
||
|
this.pendingCallback = callback;
|
||
|
|
||
|
if (buffer != null)
|
||
|
{
|
||
|
Fx.Assert(object.ReferenceEquals(this.bufferHolder[0], OverlappedContext.dummyBuffer), "StartAsyncOperation: buffer holder corrupted.");
|
||
|
this.bufferHolder[0] = buffer;
|
||
|
this.pinnedHandle.Target = this.pinnedTarget;
|
||
|
this.bufferPtr = (byte*)Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0);
|
||
|
}
|
||
|
|
||
|
if (bound)
|
||
|
{
|
||
|
this.overlapped.EventHandleIntPtr = IntPtr.Zero;
|
||
|
|
||
|
// For completion ports, the back-reference is this member.
|
||
|
this.rootedHolder.ThisHolder = this;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Need to do this since we register the wait before posting the I/O.
|
||
|
if (this.completionEvent != null)
|
||
|
{
|
||
|
this.completionEvent.Reset();
|
||
|
}
|
||
|
|
||
|
this.overlapped.EventHandleIntPtr = EventHandle;
|
||
|
|
||
|
// For unbound, the back-reference is this registration.
|
||
|
this.registration = ThreadPool.UnsafeRegisterWaitForSingleObject(this.completionEvent, OverlappedContext.eventCallback, this, Timeout.Infinite, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
|
||
|
public void CancelAsyncOperation()
|
||
|
{
|
||
|
this.rootedHolder.ThisHolder = null;
|
||
|
if (this.registration != null)
|
||
|
{
|
||
|
this.registration.Unregister(null);
|
||
|
this.registration = null;
|
||
|
}
|
||
|
this.bufferPtr = null;
|
||
|
this.bufferHolder[0] = OverlappedContext.dummyBuffer;
|
||
|
this.pendingCallback = null;
|
||
|
}
|
||
|
|
||
|
// public void StartSyncOperation(byte[] buffer)
|
||
|
// {
|
||
|
// StartSyncOperation(buffer, ref this.bufferHolder[0], false);
|
||
|
// }
|
||
|
|
||
|
// The only holder allowed is Holder[0]. It can be passed in as a ref to prevent repeated expensive array lookups.
|
||
|
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
|
||
|
public void StartSyncOperation(byte[] buffer, ref object holder)
|
||
|
{
|
||
|
if (this.syncOperationPending)
|
||
|
{
|
||
|
throw Fx.AssertAndThrow("StartSyncOperation called while an operation was already pending.");
|
||
|
}
|
||
|
if (this.pendingCallback != null)
|
||
|
{
|
||
|
throw Fx.AssertAndThrow("StartSyncOperation called while an async operation was already pending.");
|
||
|
}
|
||
|
if (this.nativeOverlapped == null)
|
||
|
{
|
||
|
throw Fx.AssertAndThrow("StartSyncOperation called on freed OverlappedContext.");
|
||
|
}
|
||
|
|
||
|
this.overlapped.EventHandleIntPtr = EventHandle;
|
||
|
|
||
|
// [....] operations do NOT root this object. If it gets finalized, we need to know not to free the buffer.
|
||
|
// We do root the event.
|
||
|
this.rootedHolder.EventHolder = this.completionEvent;
|
||
|
this.syncOperationPending = true;
|
||
|
|
||
|
if (buffer != null)
|
||
|
{
|
||
|
Fx.Assert(object.ReferenceEquals(holder, OverlappedContext.dummyBuffer), "StartSyncOperation: buffer holder corrupted.");
|
||
|
holder = buffer;
|
||
|
this.pinnedHandle.Target = this.pinnedTarget;
|
||
|
this.bufferPtr = (byte*)Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If this returns false, the OverlappedContext is no longer usable. It shouldn't be freed or anything.
|
||
|
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
|
||
|
public bool WaitForSyncOperation(TimeSpan timeout)
|
||
|
{
|
||
|
return WaitForSyncOperation(timeout, ref this.bufferHolder[0]);
|
||
|
}
|
||
|
|
||
|
// The only holder allowed is Holder[0]. It can be passed in as a ref to prevent repeated expensive array lookups.
|
||
|
[SecurityCritical]
|
||
|
public bool WaitForSyncOperation(TimeSpan timeout, ref object holder)
|
||
|
{
|
||
|
if (!this.syncOperationPending)
|
||
|
{
|
||
|
throw Fx.AssertAndThrow("WaitForSyncOperation called while no operation was pending.");
|
||
|
}
|
||
|
|
||
|
if (!UnsafeNativeMethods.HasOverlappedIoCompleted(this.nativeOverlapped))
|
||
|
{
|
||
|
if (!TimeoutHelper.WaitOne(this.completionEvent, timeout))
|
||
|
{
|
||
|
// We can't free ourselves until the operation is done. The only way to do that is register a callback.
|
||
|
// This will root the object. No longer any need for the finalizer. This instance is unusable after this.
|
||
|
GC.SuppressFinalize(this);
|
||
|
ThreadPool.UnsafeRegisterWaitForSingleObject(this.completionEvent, OverlappedContext.cleanupCallback, this, Timeout.Infinite, true);
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Fx.Assert(this.bufferPtr == null || this.bufferPtr == (byte*)Marshal.UnsafeAddrOfPinnedArrayElement((byte[])holder, 0),
|
||
|
"The buffer moved during a [....] call!");
|
||
|
|
||
|
CancelSyncOperation(ref holder);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// public void CancelSyncOperation()
|
||
|
// {
|
||
|
// CancelSyncOperation(ref this.bufferHolder[0]);
|
||
|
// }
|
||
|
|
||
|
// The only holder allowed is Holder[0]. It can be passed in as a ref to prevent repeated expensive array lookups.
|
||
|
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
|
||
|
public void CancelSyncOperation(ref object holder)
|
||
|
{
|
||
|
this.bufferPtr = null;
|
||
|
holder = OverlappedContext.dummyBuffer;
|
||
|
Fx.Assert(object.ReferenceEquals(this.bufferHolder[0], OverlappedContext.dummyBuffer), "Bad holder passed to CancelSyncOperation.");
|
||
|
|
||
|
this.syncOperationPending = false;
|
||
|
this.rootedHolder.EventHolder = null;
|
||
|
}
|
||
|
|
||
|
// This should ONLY be used to make a 'ref object' parameter to the zeroth element, to prevent repeated expensive array lookups.
|
||
|
public object[] Holder
|
||
|
{
|
||
|
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
|
||
|
get
|
||
|
{
|
||
|
return this.bufferHolder;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public byte* BufferPtr
|
||
|
{
|
||
|
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
|
||
|
get
|
||
|
{
|
||
|
byte* ptr = this.bufferPtr;
|
||
|
if (ptr == null)
|
||
|
{
|
||
|
#pragma warning suppress 56503 // [....], not a publicly accessible API
|
||
|
throw Fx.AssertAndThrow("Pointer requested while no operation pending or no buffer provided.");
|
||
|
}
|
||
|
return ptr;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public NativeOverlapped* NativeOverlapped
|
||
|
{
|
||
|
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
|
||
|
get
|
||
|
{
|
||
|
NativeOverlapped* ptr = this.nativeOverlapped;
|
||
|
if (ptr == null)
|
||
|
{
|
||
|
#pragma warning suppress 56503 // [....], not a publicly accessible API
|
||
|
throw Fx.AssertAndThrow("NativeOverlapped pointer requested after it was freed.");
|
||
|
}
|
||
|
return ptr;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
IntPtr EventHandle
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (this.completionEvent == null)
|
||
|
{
|
||
|
this.completionEvent = new ManualResetEvent(false);
|
||
|
this.eventHandle = (IntPtr)(1 | (long)this.completionEvent.SafeWaitHandle.DangerousGetHandle());
|
||
|
}
|
||
|
return this.eventHandle;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
|
||
|
static void CompleteCallback(uint error, uint numBytes, NativeOverlapped* nativeOverlapped)
|
||
|
{
|
||
|
// Empty out the AsyncResult ASAP to close the leak window.
|
||
|
Overlapped overlapped = Overlapped.Unpack(nativeOverlapped);
|
||
|
OverlappedContext pThis = ((RootedHolder)overlapped.AsyncResult).ThisHolder;
|
||
|
Fx.Assert(pThis != null, "Overlapped.AsyncResult not set. I/O completed multiple times, or cancelled I/O completed.");
|
||
|
Fx.Assert(object.ReferenceEquals(pThis.overlapped, overlapped), "CompleteCallback completed with corrupt OverlappedContext.overlapped.");
|
||
|
Fx.Assert(object.ReferenceEquals(pThis.rootedHolder, overlapped.AsyncResult), "CompleteCallback completed with corrupt OverlappedContext.rootedHolder.");
|
||
|
pThis.rootedHolder.ThisHolder = null;
|
||
|
|
||
|
Fx.Assert(pThis.bufferPtr == null || pThis.bufferPtr == (byte*)Marshal.UnsafeAddrOfPinnedArrayElement((byte[])pThis.bufferHolder[0], 0),
|
||
|
"Buffer moved during bound async operation!");
|
||
|
|
||
|
// Release the pin.
|
||
|
pThis.bufferPtr = null;
|
||
|
pThis.bufferHolder[0] = OverlappedContext.dummyBuffer;
|
||
|
|
||
|
OverlappedIOCompleteCallback callback = pThis.pendingCallback;
|
||
|
pThis.pendingCallback = null;
|
||
|
Fx.Assert(callback != null, "PendingCallback not set. I/O completed multiple times, or cancelled I/O completed.");
|
||
|
|
||
|
callback(true, (int)error, checked((int)numBytes));
|
||
|
}
|
||
|
|
||
|
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
|
||
|
static void EventCallback(object state, bool timedOut)
|
||
|
{
|
||
|
OverlappedContext pThis = state as OverlappedContext;
|
||
|
Fx.Assert(pThis != null, "OverlappedContext.EventCallback registered wait doesn't have an OverlappedContext as state.");
|
||
|
|
||
|
if (timedOut)
|
||
|
{
|
||
|
Fx.Assert("OverlappedContext.EventCallback registered wait timed out.");
|
||
|
|
||
|
// Turn this into a leak. Don't let ourselves get cleaned up - could scratch the heap.
|
||
|
if (pThis == null || pThis.rootedHolder == null)
|
||
|
{
|
||
|
// We're doomed to do a wild write and corrupt the process.
|
||
|
DiagnosticUtility.FailFast("Can't prevent heap corruption.");
|
||
|
}
|
||
|
pThis.rootedHolder.ThisHolder = pThis;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
pThis.registration = null;
|
||
|
|
||
|
Fx.Assert(pThis.bufferPtr == null || pThis.bufferPtr == (byte*)Marshal.UnsafeAddrOfPinnedArrayElement((byte[])pThis.bufferHolder[0], 0),
|
||
|
"Buffer moved during unbound async operation!");
|
||
|
|
||
|
// Release the pin.
|
||
|
pThis.bufferPtr = null;
|
||
|
pThis.bufferHolder[0] = OverlappedContext.dummyBuffer;
|
||
|
|
||
|
OverlappedIOCompleteCallback callback = pThis.pendingCallback;
|
||
|
pThis.pendingCallback = null;
|
||
|
Fx.Assert(callback != null, "PendingCallback not set. I/O completed multiple times, or cancelled I/O completed.");
|
||
|
|
||
|
callback(false, 0, 0);
|
||
|
}
|
||
|
|
||
|
[PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical]
|
||
|
static void CleanupCallback(object state, bool timedOut)
|
||
|
{
|
||
|
OverlappedContext pThis = state as OverlappedContext;
|
||
|
Fx.Assert(pThis != null, "OverlappedContext.CleanupCallback registered wait doesn't have an OverlappedContext as state.");
|
||
|
|
||
|
if (timedOut)
|
||
|
{
|
||
|
Fx.Assert("OverlappedContext.CleanupCallback registered wait timed out.");
|
||
|
|
||
|
// Turn this into a leak.
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Fx.Assert(pThis.bufferPtr == null || pThis.bufferPtr == (byte*)Marshal.UnsafeAddrOfPinnedArrayElement((byte[])pThis.bufferHolder[0], 0),
|
||
|
"Buffer moved during synchronous deferred cleanup!");
|
||
|
|
||
|
Fx.Assert(pThis.syncOperationPending, "OverlappedContext.CleanupCallback called with no [....] operation pending.");
|
||
|
pThis.pinnedTarget = null;
|
||
|
pThis.rootedHolder.EventHolder.Close();
|
||
|
Overlapped.Free(pThis.nativeOverlapped);
|
||
|
}
|
||
|
|
||
|
// This class is always held onto (rooted) by the packed Overlapped. The OverlappedContext instance moves itself in and out of
|
||
|
// this object to root itself. It's also used to root the ManualResetEvent during [....] operations.
|
||
|
// It needs to be an IAsyncResult since that's what Overlapped takes.
|
||
|
class RootedHolder : IAsyncResult
|
||
|
{
|
||
|
OverlappedContext overlappedBuffer;
|
||
|
ManualResetEvent eventHolder;
|
||
|
|
||
|
|
||
|
public OverlappedContext ThisHolder
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.overlappedBuffer;
|
||
|
}
|
||
|
|
||
|
set
|
||
|
{
|
||
|
this.overlappedBuffer = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public ManualResetEvent EventHolder
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.eventHolder;
|
||
|
}
|
||
|
|
||
|
set
|
||
|
{
|
||
|
this.eventHolder = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// Unused IAsyncResult implementation.
|
||
|
object IAsyncResult.AsyncState
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
#pragma warning suppress 56503 // [....], not a publicly accessible API
|
||
|
throw Fx.AssertAndThrow("RootedHolder.AsyncState called.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
WaitHandle IAsyncResult.AsyncWaitHandle
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
#pragma warning suppress 56503 // [....], not a publicly accessible API
|
||
|
throw Fx.AssertAndThrow("RootedHolder.AsyncWaitHandle called.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool IAsyncResult.CompletedSynchronously
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
#pragma warning suppress 56503 // [....], not a publicly accessible API
|
||
|
throw Fx.AssertAndThrow("RootedHolder.CompletedSynchronously called.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool IAsyncResult.IsCompleted
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
#pragma warning suppress 56503 // [....], not a publicly accessible API
|
||
|
throw Fx.AssertAndThrow("RootedHolder.IsCompleted called.");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|