c# - ¿Es posible eliminar ExecutionContext y Thread allocations cuando se usa SocketAsyncEventArgs?
multithreading sockets (1)
Si perfila una aplicación cliente simple que usa SocketAsyncEventArgs
, notará las asignaciones Thread
y ExecutionContext
.
El origen de las asignaciones es SocketAsyncEventArgs.StartOperationCommon
que crea una copia de ExecutionContext con ExecutionContext.CreateCopy()
.
ExecutionContext.SuppressFlow
parece una buena manera de suprimir esta asignación. Sin embargo, este método generará asignaciones cuando se ejecute en un nuevo hilo.
¿Cómo puedo evitar estas asignaciones?
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; } }
Hilo
// 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); } }
La only forma de configurar isFlowSuppressed
en true
, pasar la condición en el método StartOperationCommon
, es llamando al método Setup
, y la única llamada a la Setup
es en el método SuppressFlow
, que ha discutido.
Como puede ver,
SuppressFlow
es la única solución.