使用SocketAsyncEventArgs时,是否可能在不使用ExecutionContext和线程分配的情况下进行操作?

29
如果你调试一个使用SocketAsyncEventArgs的简单客户端应用程序,你会注意到ThreadExecutionContext的分配。
这些分配的源头是SocketAsyncEventArgs.StartOperationCommon,该方法在执行时会使用ExecutionContext.CreateCopy()创建一个ExecutionContext的副本。 ExecutionContext.SuppressFlow似乎是抑制此类分配的好方法。 但是,当在新线程中运行此方法时,它本身会生成分配。
如何避免这些分配?

2
为什么要抑制 ExecutionContext 流? - Yuval Itzchakov
3
你为什么要尝试进行微基准测试?为什么分配 ExecutionContext 是如此重要? - Yuval Itzchakov
5
我想在一个对延迟非常敏感且存在GC暂停问题的应用程序中使用 SocketAsyncEventArgs - cao
5
你考虑过使用非GC语言,比如C++吗?看起来你的应用程序可能不适合在.NET上运行。 - theMayer
2
@FrankHileman 实际上,SocketAsyncEventArgs 的重点是防止在内存中固定新分配的缓冲区 - 如果不使用 SocketAsyncEventArgs,则会出现内存碎片问题,而不是 GC 延迟。如果您只是为此操作创建了一个缓冲区,那么您有大约 100% 的机会防止堆压缩。由于 Read 调用可能需要很长时间,因此您的堆变得非常分散(我们的套接字服务器达到了超过 99% 的碎片化 - 兆字节的内存空闲,但无法重复使用)。 - Luaan
显示剩余6条评论
2个回答

2
  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 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.
    
  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);
        }
    }
    
将英文翻译成中文:

isFlowSuppressed设置为true的唯一方法,以通过StartOperationCommon方法中的条件,就是调用Setup方法,而唯一调用Setup的方法是你已经讨论过的SuppressFlow方法。

正如你所看到的,SuppressFlow是唯一的解决方案。


1
你可能想要添加一下 ExecutionContext 到底是什么,以及它的作用是什么。它不是为了好玩而存在的。 - Luaan

0
实际上,SuppressFlow 不会分配内存。它返回一个AsyncFlowControl,这是一个struct。正确的解决方案基本上是按照以下方式调用SendAsyncReceiveAsync
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();
    }
}

我创建了这些扩展方法,使得代码更加简单明了。

使用 dotMemory 进行跟踪显示,内存分配确实降至零。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接