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

时间:2015-06-07 18:56:41

标签: c# .net multithreading asynchronous async-await

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

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

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

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

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

所以,到目前为止,我所做的ProcessClientAsync方法被标记为async void,我只是将其称为ProcessClientAsync(客户端),并且该调用将立即返回给调用者。 但是我在网上看到,除非是为了一个活动,否则使用它是一个糟糕的决定。 所以我将定义更改为异步任务。

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

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

谷歌搜索我来到这个帖子: How to safely call an async method in C# without await

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

所以我这样做了:

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

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

这将是一个问题。

另一个是:我怎么能调用一个永远运行的异步Task方法,让调用立即返回调用者,这样while循环可以继续,tcp监听器可以继续接受下一个客户端?

谢谢。

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

3 个答案:

答案 0 :(得分:12)

  

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

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

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

  

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

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

然而,假设开始一个线程是首先正确的操作。大多数TCP / IP服务器应用程序不受CPU限制,所以我在这里质疑多线程的使用。完全可能有一个完全异步的TCP / IP服务器,它只为其async方法延续使用线程池。

  

另一个是:我怎么能调用一个永远运行的异步Task方法,让调用立即返回调用者,这样while循环可以继续,tcp监听器可以继续接受下一个客户端?

你只是await它返回的Task

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

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

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

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

然后将这些客户端状态存储在列表/字典中。你究竟如何做到这一点当然取决于你。

答案 1 :(得分:2)

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

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

答案 2 :(得分:0)

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

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);
  }
}