在任务中引发事件会导致订阅者方法失败

Raising an event in a Task causes the subscriber method to fail

我有一些 类 通过各种事件 "linked"。基本上是 TCPListenerClientSessionServer.

TCPListener 将新连接冒泡到 Server,然后 Server 创建一个新的 ClientSession

TCPListener 通过事件传递新套接字,ClientConnected

引发事件的方法如下所示:

    //Processes a new connection and immediately puts the listen socket back in a receiving state
    private void ProcessAccept(SocketAsyncEventArgs e)
    {
        ClientConnected(this, e));            

        StartAccept();
    }

我想避免做任何可能阻止或减慢接受新客户的事情,所以我努力通过任务引发事件。这样做的原因是用户可以覆盖 Server 中的 OnClientConnected 方法,这样它很长 运行ning 并且可能影响服务器性能。

修改后的方法如下:

    //Processes a new connection and immediately puts the listen socket back in a receiving state
    private void ProcessAccept(SocketAsyncEventArgs e)
    {
        Task.Run(() => ClientConnected(this, e));            

        StartAccept();
    }

当程序运行使用新方法时,没有崩溃异常,但服务器无法接收或发送给客户端。

这里是Server中的OnClientConnected方法:

    /// <summary>
    /// An event that is fired when a client connects.
    /// </summary>
    /// <param name="sender">The Listener that accepted the connection</param>
    /// <param name="e">The SocketAsyncEventArgs </param>
    protected virtual void OnClientConnected(object sender, EventArgs e)
    {
        ClientSession Session = ClientSessionPool.Pop();

        Session.Socket = ((SocketAsyncEventArgs)e).AcceptSocket;

        string WelcomeMessage = "Connected";

        Session.SendAsync(Encoding.Default.GetBytes(WelcomeMessage));

        this.ClientSessions.Add(Session);

        Console.Write($"\rConnected clients:{ClientSessions.Count}");
    }

只需恢复到旧的同步方法即可。

让我烦恼的是,无论是否使用 Task,通过事件传递的 SocketAsyncEventArgs 似乎都是正确的,这是唯一的失败点我能想到。

使用 Task 版本的方法时,SocketAsyncEventArgs 似乎完全错误。

我怀疑是我对分配给新线程的堆栈的理解导致了我的困惑。谁能看出我的逻辑有漏洞?谢谢!

PS

我知道我当前对 ClientSessions 列表的实现不是线程安全的,但在我的测试中,我一次只连接一个客户端。我最终解决这个问题。

PPS 这是 StartAccept 方法,以防它有用:

    //Puts the accepting TCP socket back into an accepting state
    public void StartAccept()
    {
        // socket must be cleared since the context object is being reused
        m_SocketEventArgs.AcceptSocket = null;

        bool willRaiseEvent = m_Socket.AcceptAsync(m_SocketEventArgs);
        if (!willRaiseEvent)
        {
            ProcessAccept(m_SocketEventArgs);
        }
    }

解决方案是在 UI 线程上引发您的事件,但 运行 您的事件处理是异步的。

private async void OnClientConnected(object sender, EventArgs e)
{
    await Task.Run(() => HandleClientConnected(object, e));
}

protected abstract Task HandleClientConnected(object sender, EventArgs e);