【问题标题】:Raising an event in a Task causes the subscriber method to fail在 Task 中引发事件会导致订阅者方法失败
【发布时间】:2017-02-06 22:18:52
【问题描述】:

我有一些通过各种事件“链接”的课程。基本上是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 方法,使其长时间运行并可能影响服务器性能。

这是修改后的方法:

    //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 似乎完全错误。

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

附言

我知道我当前对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);
        }
    }

【问题讨论】:

  • 你不只是在与 StartAccept 比赛吗?
  • @dlatikay 不这么认为。 StartAccept 没有触及 ClientConnected 所做的任何事情,除了 SocketAsyncEventArgs 之外,但即便如此,它也会被复制到新的 Task 的堆栈中(我认为)。我会在问题中添加StartAccept,让我知道。
  • @dlatikay 哦 SNAP!我现在看到了...m_SocketEventArgs.AcceptSocket = null;...就是这样,不是吗?

标签: c# multithreading parallel-processing task threadpool


【解决方案1】:

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

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

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

【讨论】:

  • 不确定控制台程序是否仍然算作具有“UI”线程。我不太明白这会达到什么目的......这不会一直阻塞直到 Task 完成基本上使这个方法同步和非并行?
  • @Connel.O'Donnell 我想我明白了。它只是等待任务的创建而不是它的执行吗?
  • 没错。或者您可以不等待处理程序,将其视为即发即弃的命令。
  • 嗯...不确定我是否理解...您的意思是将方法保留为async 但省略await?如果是这样的话,那就太理想了,我根本不在乎任务的结果:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-10
  • 2013-05-18
相关资源
最近更新 更多