【问题标题】:Is it recommended/acceptable to run an async void method into a new thread?是否建议/可以接受将 async void 方法运行到新线程中?
【发布时间】:2018-11-29 16:33:14
【问题描述】:

我有一段旧代码(.NET 3.5),我在一个单独的线程中运行新 TcpClient 的注册:

旧代码:

public class TcpServer
{
    private readonly TcpListener _tcpListener;
    
    public TcpServer(int port)
    {
        _tcpListener = new TcpListener(IPAddress.Any, port);
    }
    
    public void StartListening()
    {
        _tcpListener.Start();

        var t = new Thread(new ThreadStart(AcceptConnections));
        t.Start();
    }
    
    private void AcceptConnections()
    {
        while (true)
        {
            var client = _tcpListener.AcceptTcpClient();
            
            // Some stuff
        }
    }
}

此代码的目的是避免在等待新客户端时阻塞应用。我正在将此代码移植到 .NET Standard,我想知道是否必须使用 .NET 的新功能。

我说的是async/awaitTask

异步线程版本

由于这个线程将持续整个应用程序,我首先考虑用新的异步方法简单地替换实际方法。但如果我这样做,我最终会得到一个运行async void 方法的新线程:

public class TcpServer
{
    private readonly TcpListener _tcpListener;
    
    public TcpServer(int port)
    {
        _tcpListener = new TcpListener(IPAddress.Any, port);
    }
    
    public void StartListening()
    {
        _tcpListener.Start();

        var t = new Thread(new ThreadStart(AcceptConnectionsAsync));
        t.Start();
    }
    
    private async void AcceptConnectionsAsync()
    {
        while (true)
        {
            var client = await _tcpListener.AcceptTcpClientAsync();
            
            // Some stuff
        }
    }
}

不建议编写async void 方法,除非在极少数情况下(如事件处理程序)。

这种情况可以接受吗?

异步任务版本

作为一个新选项,我在Task 中运行注册:

public class TcpServer
{
    private readonly TcpListener _tcpListener;
    
    public TcpServer(int port)
    {
        _tcpListener = new TcpListener(IPAddress.Any, port);
    }
    
    public Task StartListeningAsync()
    {
        _tcpListener.Start();

        return Task.Run(async () => await AcceptConnectionsAsync());
    }
    
    private async Task AcceptConnectionsAsync()
    {
        while (true)
        {
            var client = await _tcpListener.AcceptTcpClientAsync();
            
            // Some stuff
        }
    }
}

有了这段代码,只要我不awaitAcceptConnectionsAsync调用,应用程序就不会被阻塞(我用while(true)简化了代码,但我处理了应用程序的关闭和结束正确地听,我可以在那里调用 await ,甚至可以忽略它)。

但正如我所说,这个动作应该和整个应用程序一样长,我认为一个新线程更合适(至少在架构方面,但在效率方面也是如此)。

我错了吗?

不要对这部分代码做任何改动

这是我看到的最后一个选项。我打算用 Async/Await 更新代码,因为我读到 async/await 提高了 TcpListener 的性能。

但这是真的吗?

重要信息

以下是一些我认为可能相关的信息。不要犹豫询问更多细节。

  • 运行此代码的应用程序是控制台应用程序。我知道这对于异步/等待上下文可能很重要?
  • AcceptConnections(Async) 方法为每个新连接运行一个新任务(这些任务是短暂的,属于即发即弃类型)。
  • 主线程执行大量计算。最好的解决方案是对主线程性能的影响最小。
  • 还有一个其他线程的行为与此类似(持续时间相同),负责侦听来自所有客户端的数据。我打算应用与此相同的操作。

【问题讨论】:

  • Is this case acceptable? 是事件处理程序吗?为什么要使用async void 而不是async Taskstackoverflow.com/questions/44364092/… 有帮助吗?
  • 因为ThreadStart 构造函数只接受返回void 的委托。
  • 你为什么要使用ThreadStart vs Task
  • 在我的研究中没有找到您的链接......太糟糕了,它回答了我的部分问题。关于你的第二个问题,我读到Task 不适合长期行动。这就是为什么我希望改用Thread

标签: c# multithreading async-await task


【解决方案1】:

但正如我所说,此操作应该持续与整个应用程序一样长

不,它没有。该方法将几乎立即返回。这就是异步方法的含义。它不会在工作完成时阻止调用者。

创建一个新线程只是为了调用一个异步方法,就像雇人来你家为你在邮箱里放一封信一样。这比你自己做更多的工作,因为将信送到目的地的实际行为实际上并不会阻止你做一些微不足道的工作之外的工作来开始它。

【讨论】:

  • 整个AcceptConnectionsAsync 方法将与应用程序一样长。对 AcceptTcpClientAsync 的调用在 foreach 循环中。
  • @fharreau 该方法将在相当长的一段时间内异步工作。可能只要应用程序正在进行,当然可以。但是方法立即返回。您实际上在另一个线程中运行的唯一工作是 start 操作的一小部分工作。其余的都不会受到任何影响。
  • 我明白你的意思。我无法在主线程中等待该调用,因为我需要在该线程中执行其他操作(并且我需要为每个新连接等待它)。这就是我最初在新线程中使用阻塞调用运行该方法的原因。可能我只是不能使用 Async/Await 吗?
  • @fharreau 如果您不希望在方法永远完成后运行其余代码,那么不要await 它。 await 只是编写将在异步方法完成时运行的代码的简单方法。你不需要为了不等待而创建一个新线程。
  • 那么,AcceptConnectionsAsync方法中var client = await _tcpListener.AcceptTcpClientAsync();之后的代码在哪里执行?在另一个线程中?由于这段代码处于永远循环中,不会影响主线程的性能吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-22
  • 2019-12-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多