返回Task而不是Task是完全错误的 ?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了返回Task而不是Task是完全错误的 ?相关的知识,希望对你有一定的参考价值。

任务API感觉有点像一个失败的陷阱,考虑到我可以用它做的每一件事可能带来的更多DO DO而不是DO。但我试图更多地使用它,我对async void的整个问题感到困惑。

我的问题发生在我的应用程序永远使用本地数据文件的地方,并且因为它使用了快速同步调用。现在一些远程源是一个选项,它们有足够的延迟我将它全部切换为异步。

所以我有一个方法可能看起来像这样:

void InitializeData() {
    SomeProperty = _dataSource.Load(...);
}

我想为低摩擦做的天真的事情是:

Task InitializeDataAsync() {
    return Task.Run(() => {
        SomeProperty = _dataSource.Load(...);
    });
}

但是有很多关于使用async void有多糟糕的文章,他们似乎在讨论返回任务时模糊不清。所以我最好的选择就是这样写:

Task<DataType> FetchDataAsync() {
    return _dataSource.LoadAsync(...);
}

...然后追捕所有来电者并让他们遵守?

我觉得人们提出的论点是async void是坏的,因为当你使用async Taskawait和可怕的UnobservedTaskException的不同行为。在这种情况下,我不太可能使用await。或者,如果我使用await,它总是会有一个try / catch,因为我们已经对UnobservedTaskException产生了偏执。

所以:

  • “不要使用async void”与“不要使用Task”相同吗?
  • 如果你不使用await,它会缓解吗?
  • 如果你确实使用await,总是用try / catch包装它可以缓解这个问题吗?
答案

async void与返回Task不同。 async void的问题是没有办法观察任务异常(除了全局异常处理程序),并且没有办法等待任务完成(你实际上不能await一个async void方法,因为await没有任何东西),这就是应该避免的原因。

返回Task没有这些问题,如果你知道它完成时你知道await你会得到例外,等待所以返回一个Task是完全可以接受的。

另一答案

看起来你只是困惑。 await / async API非常简单。唯一令人困惑的部分是了解the difference between await/async API and the Task API。我不会详细介绍这一点,因为Stephen Cleary的博客清楚地涵盖了这一点,但这里有重要的摘录:

要重申我上一篇文章中的一句话,请使用Task.Run调用CPU绑定方法(来自GUI线程)。不要仅仅使用它来“为我的异步方法提供一些可供使用的东西”。

下一个...

我想为低摩擦做的天真的事情是:

Task InitializeDataAsync() {
  return Task.Run(() => {
    SomeProperty = _dataSource.Load(...);
  });
}

这是将Sync代码卸载到Async包装器中的一个明显案例,它实际上没有任何好处。

但是有很多关于使用async void有多糟糕的文章,

这显然不是async void方法。这是一种非异步的Task返回方法。这是一个异步void方法:

async void InitializeDataAsync() {
  await Task.Run(() => {
    SomeProperty = _dataSource.Load(...);
  });
}

关于返回任务的讨论似乎很模糊。

我没有看到任何模糊这些信息的内容。这很简单;如果您有async方法,请始终返回TaskTask<T>(除非如下所述)。使用这两个(TaskTask<T>)之间的区别纯粹是设计/架构。

所以我最好的选择就是这样写:

Best在这里真的是一个意见。这两个工作,不要忘记async/wait

async Task<DataType> FetchDataAsync() {
  return await _dataSource.LoadAsync(...);
}

async Task FetchDataAsync() {
  _data = await _dataSource.LoadAsync(...);
}

我觉得人们提出的论点是异步void是坏的,因为async Task的行为不同,当你使用await时可怕的UnobservedTaskException。

The most important difference is how exceptions are handled

摘抄:

异步void方法具有不同的错误处理语义。当异步任务或异步任务方法抛出异常时,将捕获该异常并将其放在Task对象上。使用async void方法时,没有Task对象,因此异步void方法抛出的任何异常都将直接在async void方法启动时处于活动状态的SynchronizationContext上引发。

所以

或者,如果我使用await,它总是会有一个try / catch,因为我们已经对UnobservedTaskException产生了偏执。

这意味着您无法通过try / catch绕过异步void来捕获异常。这是一个很大的问题!

“不要使用异步空白”和“不要使用任务吗?”相同。

那么这是苹果和橘子。是的:除非你将它们用于async void,否则不要使用async event handlers

不要使用Task,除非它们是CPU绑定的,或者你正在使用Framework调用(或者你已经正确包装了自己使用IO Completion Ports的方法)。

如果你不使用await,它会减轻吗?

希望现在您了解await / async与Task不同,您要做的是编写所有await / async代码。

如果你确实使用了await,那么总是用try / catch包装它可以缓解这个问题吗?

如上所述,取决于。

以上是关于返回Task而不是Task是完全错误的 ?的主要内容,如果未能解决你的问题,请参考以下文章

我从只返回 Task 而不是 Task<T> 的方法返回啥?

如何从异步函数 C# 返回字符串而不是 SYSTEM.THREADING.TASK.TASK`1[SYSTEM.STRING]

Task<Task> 在 C# 中是非法的吗?

你如何设置一些函数在后台线程中运行但保持正常的返回类型而不是 Task<int>?

为啥 ExecutorService.submit(Runnable task) 返回 Future<?> 而不是 Future<Void>?

为啥要在 C# 中使用 Task<T> 而不是 ValueTask<T>?