private object WaitForCompletion(bool snap)
{
ManualResetEvent waitHandle = null;
bool createdByMe = false;
bool complete = snap ? IsCompleted : InternalPeekCompleted;
if (!complete)
{
// Not done yet, so wait:
waitHandle = (ManualResetEvent)_event;
if (waitHandle == null)
{
createdByMe = LazilyCreateEvent(out waitHandle);
}
}
if (waitHandle != null)
{
try
{
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"Waiting for completion event {waitHandle}");
waitHandle.WaitOne(Timeout.Infinite);
}
catch (ObjectDisposedException)
{
// This can occur if this method is called from two different threads.
// This possibility is the trade-off for not locking.
}
finally
{
// We also want to dispose the event although we can't unless we did wait on it here.
if (createdByMe && !_userEvent)
{
// Does _userEvent need to be volatile (or _event set via Interlocked) in order
// to avoid giving a user a disposed event?
ManualResetEvent oldEvent = (ManualResetEvent)_event;
_event = null;
if (!_userEvent)
{
oldEvent.Dispose();
}
}
}
}
// A race condition exists because InvokeCallback sets _intCompleted before _result (so that _result
// can benefit from the synchronization of _intCompleted). That means you can get here before _result got
// set (although rarely - once every eight hours of stress). Handle that case with a spin-lock.
SpinWait sw = new SpinWait();
while (_result == DBNull.Value)
{
sw.SpinOnce();
}
if (NetEventSource.IsEnabled) NetEventSource.Exit(this, _result);
return _result;
}