如何在新线程上运行任务并立即返回给调用者?

Posted

技术标签:

【中文标题】如何在新线程上运行任务并立即返回给调用者?【英文标题】:How to run a Task on a new thread and immediately return to the caller? 【发布时间】:2015-08-22 05:17:18 【问题描述】:

在过去的几个月里,我一直在阅读 C# 中的 async-await 以及如何正确使用它。

出于实验室练习的目的,我正在构建一个小型 Tcp 服务器,它应该为连接到它的客户端提供服务。该程序是一个控制台应用程序。

我使用 while 循环来等待这样的连接:

while (!_isStopRequested)

       TcpClient client = await _tcpListener.AcceptTcpClientAsync();

       await Task.Factory.StartNew(() => ProcessClientAsync(client), TaskCreationOptions.LongRunning);
 

所以,到目前为止,我创建的方法 ProcessClientAsync 被标记为 async void,我只需将其称为 ProcessClientAsync(client),该调用将立即返回给调用者。 但是,我在互联网上读到,除非用于活动,否则使用它是一个糟糕的决定。 所以我将定义更改为异步任务。

好的,但没有等待,我在 Visual Studio 中收到警告“因为未等待此调用,当前方法在调用完成之前继续运行”。

一旦我使用了 await ProcessClientAsync(client),代码就不起作用了。我只能连接一个客户端,然后调用者“等待” ProcessClientAsync 返回。但是,那个方法有一个无限循环并且不会返回,但是我需要while循环来继续处理下一个客户端。

谷歌搜索我想出了这个线程: How to safely call an async method in C# without await

我想问题几乎相同,除了当我使用 await ProcessClientAsync 时,我希望它立即返回给调用者,但它不会这样做,我知道因为运行第二个客户端,而第一个是仍在运行,第二个客户端未连接。

所以我这样做了:

await Task.Factory.StartNew(() => ProcessClientAsync(client), TaskCreationOptions.LongRunning);

但是由于 ProcessClientAsync 必须返回一个任务,我不确定这样做是否可以?

这是一个问题。

另一个是:我如何调用一个将永远运行的异步任务方法,并让调用立即返回给调用者,以便 while 循环可以继续并且 tcp 侦听器可以继续接受下一个客户端?

谢谢。

抱歉,如果是重复,或者不清楚我在问什么。

【问题讨论】:

请缩小您的问题范围:它过于宽泛。另外,为了更好的可读性,请进行一些编辑,不包括非必要部分。谢谢和问候, 【参考方案1】:

这将启动任务并返回给调用者,并避免编译器警告:

var t = Task.Factory.StartNew(() => );

【讨论】:

【参考方案2】:

如果我正确理解您的问题,那么这段代码应该会有所帮助:

private async void AcceptClient(TcpClient client)

  await Task.Factory.StartNew(() => ProcessClientAsync(client), TaskCreationOptions.LongRunning);


private async void Run()

  while (!_isStopRequested)
  
       TcpClient client = await _tcpListener.AcceptTcpClientAsync();
       AcceptClient(client);
  
 

【讨论】:

OP 根据指南从 void 切换到 Task,但我认为这种情况非常适合异步 void 方法的指南。 我确实想到使用 async void 可能没问题,但我想“按部就班”,因此改为 async Task【参考方案3】:

出于实验室练习的目的,我正在构建一个小型 Tcp 服务器,它应该为连接到它的客户端提供服务。该程序是一个控制台应用程序。

嗯,我建议的第一件事是尝试一个更简单的练习。说真的,异步 TCP 服务器是您可以选择的最复杂应用程序之一。绝大多数使用 async 的开发人员正在开发的应用程序要简单两个数量级

尝试编写一个从 URL 下载文件的 UI 应用程序。这是一个更现实的学习起点async

但是由于 ProcessClientAsync 必须返回一个任务,我不确定这样做是否可以?

我建议使用Task.Run 而不是Task.Factory.StartNewTask.Run 知道 async 方法,而 StartNew 不知道。

但是,这首先是假设启动线程是正确的操作。大多数 TCP/IP 服务器应用程序不受 CPU 限制,因此我在这里质疑多线程的使用。完全有可能拥有一个仅将线程池用于其async 方法延续的完全异步 TCP/IP 服务器。

另一个是:我如何调用一个将永远运行的异步任务方法,并让调用立即返回给调用者,以便 while 循环可以继续并且 tcp 侦听器可以继续接受下一个客户端?

你只是不await它返回的Task

Task.Run(() => ProcessClientAsync(client));

但是,编译器会在这里抱怨,因为这段代码几乎肯定是错误的。如果您忽略返回的任务,那么您将忽略该代码中的任何异常。

TCP/IP 服务器应用程序中的一个常见模式是为每个连接的客户端维护一个小的状态集合。这是放置套接字对象本身和表示处理该套接字连接的任务的自然位置:

var clientState = new ClientState(client);
clientState.Task = Task.Run(() => ProcessClientAsync(client));

然后将这些客户端状态存储在列表/字典中。具体怎么做当然取决于你。

【讨论】:

感谢您的全面回答。我尝试了您的建议并创建了一个接受 tcp 客户端的类 ClientState。然后我做了 clientState.Task = Task.Run(() => ProcessClientAsync(client));正如你所建议的那样,但它的行为就像我等待时一样。我把那段代码放在while循环中。 @Marin:如果你不awaitTask(也不要阻塞它),那么主线程将继续执行。 谢谢你,你是对的,错误在别的地方!一个非常奇怪的错误...我决定将客户端状态放入并发包中,并使用 Add 方法进行添加,但忘记实例化它...奇怪的是,我没有得到空引用异常?!这怎么可能?!总而言之,它现在可以工作了......谢谢!

以上是关于如何在新线程上运行任务并立即返回给调用者?的主要内容,如果未能解决你的问题,请参考以下文章

如何将一个批处理文件中的值返回给调用者批处理文件

Apache Camel JMS - 异常未通过请求/回复返回给调用者

同步/异步-阻塞/非阻塞

jQuery Ajax:返回值给调用者?

批处理文件:返回给调用者?

接口项目servlet的一种处理方式,将异常返回给调用者我