If you profile a simple client application that uses SocketAsyncEventArgs
, you will notice Thread
and ExecutionContext
allocations.
The source of the allocations is SocketAsyncEventArgs.StartOperationCommon
that creates a copy of the ExecutionContext with ExecutionContext.CreateCopy()
.
ExecutionContext.SuppressFlow
seems like a good way to suppress this allocation. However this method itself will generate allocations when ran in a new thread.
How can I avoid these allocations?
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 Reader internal 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_required public 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With