【问题标题】:Run async function on background thread?在后台线程上运行异步函数?
【发布时间】:2020-03-05 05:35:32
【问题描述】:

我正在开发一个包含服务器和客户端的项目。 客户端每秒向服务器发送一个 UDP 数据包,根据服务器的响应,它可能会打开与服务器的 TCP 连接(并从服务器接收文件),客户端的程序有一个我不想阻止的 GUI (在 WPF MVVM 中制作),并且在 MainWindow 的构造函数上启动异步函数也是有问题的,因为构造函数不能是异步的。 所以我的问题是,我可以并且应该在后台线程上运行异步函数吗?如果异步在后台线程上是否会提高性能,我说的是这些选项之间的区别:

public MainWindow()
{
    InitializeComponent();
    DirectoryViewModel DirectoryVM = new DirectoryViewModel();
    this.DataContext = DirectoryVM;
    DirectoryVM.StartListen(); //Unwanted as constructor won't finish
}

public MainWindow()
{
    InitializeComponent();
    DirectoryViewModel DirectoryVM = new DirectoryViewModel();
    this.DataContext = DirectoryVM;
    Task.Run(DirectoryVM.StartListen()); //Possible but async might be faster
}

public MainWindow()
{
    InitializeComponent();
    DirectoryViewModel DirectoryVM = new DirectoryViewModel();
    this.DataContext = DirectoryVM;
    Task.Run(async () => await DirectoryVM.StartListenAsync()); //Is it faster than the second option?
}

据我所见,我不应该像这样在后台线程上运行异步代码,但为什么呢?它不是比在后台线程上运行同步代码更快吗? 另外我想这并没有什么不同,但是在我的服务器上,我将创建一个持续运行的线程来侦听 tcp 连接并通过它发送文件,我是否应该使发送文件函数异步?

谢谢!

【问题讨论】:

  • 你在哪里启动后台线程?我只能看到Task.Run
  • 一般来说是一个单独的线程,对不起我的理解失误。 Task.Run 是否排队到线程池?
  • 第三个选项只有似乎更快,因为您不需要等待它完成。当MainWindow 退出时,DirectoryVM.StartListenAsync 可能不完整,更糟糕的是,您无法知道它何时实际完成。
  • 在构造函数中运行异步代码也是incredibly bad 的想法。
  • 要想得到好的答案,我们必须知道StartListen[Async]() 做了什么。但看起来第一个 sn-p 就可以了。您期望这里有什么样的性能差异?这将是纳秒。

标签: c# multithreading asynchronous async-await task


【解决方案1】:

...可以在单独的线程上启动异步方法吗...

我认为您从根本上误解了异步方法。调用异步方法不会创建新线程或将整个方法卸载到任何其他线程。调用异步方法就像调用任何其他方法一样,但它可能在某些时候将执行返回给调用者(以 Task 作为承诺)并在某个时候完成其剩余的工作。

虽然可以使用异步方法启动一个始终运行的周期性侦听器,但这与其目的相反。

当您调用异步方法时,您希望它会在合理的时间内运行完成(因此您要等待它),但同时执行其他操作可能需要足够长的时间。在您的情况下,您应该明确启动一个新的后台任务或线程定期检查什么。它可以是一个新线程或更好的新任务(例如 Task.Run)。

据我所知,我不应该在后台线程上运行异步代码 像这样,但为什么呢?

根据您的 cmets DirectoryVM.StartListen() 开始不断运行的侦听,不必是异步的。除非它执行任何异步调用,否则它不是可等待的。

这不是比在后台线程上运行同步代码更快吗?

异步不是关于速度,而是关于线程阻塞。线程是前台还是后台都没有关系,使用异步方法进行 I/O 操作,例如调用 http 端点或发送 UDP 数据包总是有益的,如果线程可以在等待时做其他事情,或者以其他方式阻塞线程可能会导致其他问题。

另外我想这并没有什么不同,但在我的服务器上我会创建一个 持续运行的线程,监听 tcp 连接并将发送 文件,我应该使发送文件功能异步吗?

如果有好处,你应该这样做。请参阅上一部分。

【讨论】:

  • 我不确定我是否同意应避免异步方法中的定期操作。我看不出有什么问题。
  • 如果我调用异步方法,我不希望它具有应用程序的生命周期假设您有以下方法:async Task DoForever() { while(true) { await SomeCall(); await Task.Delay(X); } } 现在,如果您知道此方法的工作原理,您可以调用DoForever() 但调用 await DoForever() 将阻止执行。我认为应该避免这种永远运行的异步方法。
  • DoForever 方法(或更好的DoForeverAsync),就目前而言,将返回一个不应等待的即发即弃的任务。等待这个任务永远阻塞应该不足为奇。想象一下有人写await Task.Delay(-1),然后惊讶地发现下一行代码永远不会到达。这个人急需一些咖啡!一个可能更好的设计是接受 CancellationToken 参数,然后在应用程序的终结器取消令牌和 await 任务,以保持整洁。
  • 感谢您的回答。只是为了确保,在 tcp 线程上异步发送文件对我是有益的,因为它会在等待 tcp 发送完成时释放线程来处理其他事情,对吗?而且,async/await 并没有让我的代码运行得更快(除非我猜它是异步并行的),但它是有益的,因为它不会阻塞线程,所以它在某种程度上有助于提高效率,对吧?
【解决方案2】:

据我所见,我不应该像这样在后台线程上运行异步代码,但为什么呢?

我不知道有什么建议不要使用 Task.Run 运行异步代码。

Task.Run 应避免在 ASP.NET 上使用(对于同步和异步代码),但这不适用于这里,因为您有一个 GUI 应用程序。

这不是比在后台线程上运行同步代码更快吗?

没有。代码不会更快。 async 是关于释放线程,而不是运行得更快。

我认为您所看到的问题是忽略任务。使用Task.Run 并忽略它返回的任务是有问题的,因为这是一种即发即弃的形式。因此,如果您的循环由于异常而失败,您的应用程序永远不会知道。解决此问题的一种方法是从async void 方法中await 任务。例如,作为 Window 的 Initialized 事件处理程序:

async void Window_Initialized(object, EventArgs)
{
  await Task.Run(DirectoryVM.StartListen);
  // or, if asynchronous:
  await DirectoryVM.StartListenAsync();
}

【讨论】:

  • 您好,感谢您的回答。关于在后台线程上运行异步代码-我试图查找信息但找不到太多信息,同时正如您所说,我对忽略任务的担忧感到困惑,直到今天我才看到您的回答一个问题,当向 Task.Run 发送异步函数时,Task.Run 知道等待它,所以我不必添加异步 lambda,我理解正确吗?关于客户端代码 - 有人告诉我有关计时器类的信息,所以我只需订阅 tick 事件并将其设置为 second 并运行句柄函数。
  • 还有一个问题 - 如果我不应该忽略 Task.Run 返回的任务,我应该在我的服务器代码中做什么,我总是监听 udp 消息,同时监听 tcp 连接并且对于每个 tcp 连接发送/接收文件(顺便说一下,服务器是控制台应用程序,而不是 GUI 应用程序,至少现在)?只是为了确保我理解正确 - 异步是关于不阻塞线程,所以在 UI 线程中为了不阻塞 UI 是有益的,在线程池中这样线程可以处理其他事情而不是等待?跨度>
  • Task.Run 不需要单独的 lambda;它需要一个委托,因此您可以直接向它传递一个方法。我将您的问题解释为关于客户端的问题;为避免忽略任务,您的代码最终应await 任务;如有必要,这可以在async void 方法中完成。您对客户端与服务器的好处的理解是正确的。
  • 如果我不想等待呢?调用方法后我想做其他事情(如果我说的是我需要回去听),我该怎么办?我可以忽略任务(在我的情况下,如果它有异常,我认为我不会太在意),或者我可以保存任务但不等待它,将其添加到列表中,以及每当它的状态是 RanToCompletion,从列表中删除它,如果它的错误处理异常。这是个好主意吗?
  • 你可以做列表的事情。或者像我推荐的那样,await 将任务放在 async void 方法包装器中。
猜你喜欢
  • 1970-01-01
  • 2021-07-24
  • 2019-08-29
  • 2023-04-05
  • 1970-01-01
  • 1970-01-01
  • 2019-01-07
  • 2011-06-25
  • 1970-01-01
相关资源
最近更新 更多