【问题标题】:Ignoring the return value from an async Task method忽略异步任务方法的返回值
【发布时间】:2017-08-07 21:07:34
【问题描述】:

以下是场景:在我的 WPF 应用程序中,我希望始终保持循环运行以执行各种操作。我想到了这种模式:

    void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
    {
        SomeProcessAsync(); //throw away task
    }

    async Task SomeProcessAsync()
    {
        while (true)
        {
            DoSomething();

            await Task.Delay(1000);
        }
    }

由于返回值未使用,调用会触发警告。消除该警告的最干净的方法是什么?

#pragma warning disable 4014
            AddItemsAsync(); //throw away task
#pragma warning restore 4014

这可行,但它看起来很讨厌!

顺便说一句,我也可以使用计时器,但我喜欢这个循环的简单性。

【问题讨论】:

  • 使用void作为返回类型怎么样?
  • 为什么一开始有两种方法?只需将循环放在MainWindow_OnLoaded 中即可。
  • 你为什么不用计时器来代替这个循环呢? Task.Delay 本身在下面使用了一个计时器。这不是一劳永逸的任务,只要表单或应用程序处于活动状态,它就应该处于活动状态
  • 如果我不需要它的特殊行为,我想避免 async void。此外,方法 SomeProcessAsync 不需要知道调用它的代码。它应该公开任务,并且是调用者的工作来丢弃它。
  • @Servy 好点,但假设负载处理程序已经很大,问题再次变得相关。

标签: c# async-await compiler-warnings


【解决方案1】:

Async-await 使用线程池中的线程。虽然线程池中的线程数相当大,并且可能是可调的,但它仍然是有限的线程数。

线程池中的线程针对短期任务进行了优化。它们开始和结束的速度很快,这些线程的结果可以很容易地访问。但这些优势是有代价的。如果不是,所有线程都是线程池中的线程。

如果你想让你的线程在相当长的时间内做某事,最好使用常规的 System.Threading.Thread,可能包装在 System.ComponentModel.BackgroundWorker 中。

【讨论】:

    【解决方案2】:

    正如 chris 的回答中已经提到的,这里正确的解决方案是将事件处理程序转换为 async void 方法,然后使用 await,以便正确传播异常。

    但是如果你真的想忽略Task,那么你可以将它赋值给一个变量:

    var ignored = SomeProcessAsync();
    

    或者在 C# 7.0 中,您可以使用丢弃:

    _ = SomeProcessAsync();
    

    【讨论】:

    • 有趣的是,这里没有编译器警告。编译组肯定想到了这个案例。
    【解决方案3】:

    我的解决方案是使用可重复使用的小辅助方法来消除编译器警告:

    static class TaskHelpers
    {
        /// <summary>Signifies that the argument is intentionally ignored.</summary>
        public static void DiscardTask(this Task ignored)
        {
        }
    }
    

    调用看起来像这样:

    AddItemsAsync().DiscardTask();
    

    这是干净和自我记录的。仍在为助手寻找更好的名称。

    【讨论】:

    • 检查thisthis
    • 在意大利,DiscardTask 扩展方法将被称为ForgetAboutIt。 ?
    【解决方案4】:

    最好使用您的委托代码创​​建任务 Task.Factory.StartNew( () => {} );

    【讨论】:

    • 这将无法访问 UI 控件。
    • 当然可以,UI调用使用Dispatcher.BeginInvoke++++
    • @GCamel 听起来您以前从未使用过 TPL。在使用 TPL 时,如果您调用 BeginInvoke,几乎可以肯定您做错了什么。无论如何,这并不能以任何方式解决问题。您仍在丢弃Task,您只是在创建额外的工作,在此过程中没有任何成果(并且会积极损害大量代码)。
    • @GCamel 不,他们不是。它们是一种等待已执行任务的方法。至于挑选 Servy ......好吧,你没有得到async/await 的金徽章来发表评论。 ViewModels 和 databinding 也与当前问题无关。
    【解决方案5】:

    您可以使事件处理程序异步:

    async void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
    {
        await SomeProcessAsync(); //throw away task
    }
    

    通常,async void 不好,但是当事件处理程序是异步的并且应该在此处处理异常而不是在任何调用 this 时处理异常时,这是必要的。如果 SomeProcessAsync 不需要 UI 上下文,您可以(并且应该)使用普通的 ConfigureAwait(false)

    【讨论】:

    • 重新发布之前的评论,因为它完全适用:如果我不需要它的特殊行为,我想避免异步无效。此外,方法 SomeProcessAsync 不需要知道调用它的代码。它应该公开任务,并且它是调用者的工作来丢弃它。也许在这段简单的代码中,这种技术是可以的,但我想找到一个通用的解决方案。
    • 因此,您正在寻找的解决方案是当您希望同步方法利用异步方法时。事件处理程序不是一个很好的例子,因为它可以是异步的。在原始问题中包含该要求会很好。我不确定async void 的具体行为是什么时候你想要它 - 据我记得,这是一个问题,但你打算捕获的异常转义事件处理程序也是一个问题.此外,此代码对SomeProcessAsync 没有任何要求。
    • @boot4life 调用异步方法并将任务放到地板上在功能上与async void 方法相同。 async void method 的问题在于您强迫调用者将任务放在地板上,这就是为什么在实际上不是所需行为时使用它会出现问题。
    猜你喜欢
    • 2019-02-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-22
    • 2022-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多