Is it possible to remove ExecutionContext and Thread allocations when using SocketAsyncEventArgs?
SocketAsyncEventArgs
public class SocketAsyncEventArgs : EventArgs, IDisposable {//...// Method called to prepare for a native async socket call.// This method performs the tasks common to all socket operations.internal void StartOperationCommon(Socket socket) { //... // Prepare execution context for callback. if (ExecutionContext.IsFlowSuppressed()) { // This condition is what you need to pass. // Fast path for when flow is suppressed. m_Context = null; m_ContextCopy = null; } else { // Flow is not suppressed. //... // If there is an execution context we need //a fresh copy for each completion. if(m_Context != null) { m_ContextCopy = m_Context.CreateCopy(); } } // Remember current socket. m_CurrentSocket = socket; } [Pure] public static bool IsFlowSuppressed() { return Thread.CurrentThread.GetExecutionContextReader().IsFlowSuppressed; } //... }
ExecutionContext
[Serializable] public sealed class ExecutionContext : IDisposable, ISerializable{//...// Misc state variables.private ExecutionContext m_Context;private ExecutionContext m_ContextCopy;private ContextCallback m_ExecutionCallback;//...internal struct Reader{ ExecutionContext m_ec; //... public bool IsFlowSuppressed { #if !FEATURE_CORECLR [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif get { return IsNull ? false : m_ec.isFlowSuppressed; } } } //end of Readerinternal bool isFlowSuppressed { get { return (_flags & Flags.IsFlowSuppressed) != Flags.None; } set { Contract.Assert(!IsPreAllocatedDefault); if (value) _flags |= Flags.IsFlowSuppressed; else _flags &= ~Flags.IsFlowSuppressed; } }[System.Security.SecurityCritical] // auto-generated_requiredpublic static AsyncFlowControl SuppressFlow(){ if (IsFlowSuppressed()) { throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_CannotSupressFlowMultipleTimes")); } Contract.EndContractBlock(); AsyncFlowControl afc = new AsyncFlowControl(); afc.Setup(); return afc;}//...}//end of ExecutionContext.
AsyncFlowControl
public struct AsyncFlowControl: IDisposable{private bool useEC;private ExecutionContext _ec;//... [SecurityCritical]internal void Setup(){ useEC = true; Thread currentThread = Thread.CurrentThread; _ec = currentThread.GetMutableExecutionContext(); _ec.isFlowSuppressed = true; _thread = currentThread;}}
Thread
// deliberately not [serializable][ClassInterface(ClassInterfaceType.None)][ComDefaultInterface(typeof(_Thread))][System.Runtime.InteropServices.ComVisible(true)]public sealed class Thread : CriticalFinalizerObject, _Thread{ //... [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] internal ExecutionContext.Reader GetExecutionContextReader() { return new ExecutionContext.Reader(m_ExecutionContext); }}
The only way to set isFlowSuppressed
to true
, to pass the condition in the StartOperationCommon
method, is by calling Setup
method, and the only call to Setup
is in SuppressFlow
method, wich you have discussed.
As you can see,
SuppressFlow
is the only solution.
Actually, SuppressFlow
doesn't allocate. It returns a AsyncFlowControl
, which is a struct
. The proper solution basically is to call SendAsync
and ReceiveAsync
as follows:
public static bool SendAsyncSuppressFlow(this Socket self, SocketAsyncEventArgs e){ var control = ExecutionContext.SuppressFlow(); try { return self.SendAsync(e); } finally { control.Undo(); }}public static bool ReceiveAsyncSuppressFlow(this Socket self, SocketAsyncEventArgs e){ var control = ExecutionContext.SuppressFlow(); try { return self.ReceiveAsync(e); } finally { control.Undo(); }}
I created these extension methods to make this a bit simpler and more explicit.
Traces with dotMemory showed that memory allocations really do go down to zero.