Is it possible to remove ExecutionContext and Thread allocations when using SocketAsyncEventArgs? Is it possible to remove ExecutionContext and Thread allocations when using SocketAsyncEventArgs? multithreading multithreading

Is it possible to remove ExecutionContext and Thread allocations when using SocketAsyncEventArgs?


  1. 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;    }   //...    }
  2. 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.
  3. 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;}}
  4. 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.