_ = 任务运行与异步无效 |任务.运行 vs 异步子



在控制台应用程序中。我需要从我的主线程加载一些长时间运行的代码(网络内容,REST 调用(。我想将其传递给后台线程,并且不阻止调用线程。我将在该方法中调用事件来处理它的结果。

这样做有什么区别吗,

public async Task MainThreadAsync() {
_ = Task.Run(async () => await DoSomethingAsync());
// Continue with other stuff and don't care about DoSomethingAsync()
}
private async Task DoSomethingAsync() {
// Doing long running stuff
}

还是这样做?

public async Task MainThreadAsync() {
DoSomethingAsync();
// Continue with other stuff and don't care about DoSomethingAsync()
}
private async void DoSomethingAsync() {
// Doing long running stuff
}
<小时 />

VB.Net:

Public Async Function MainThreadAsync() As Task 
Task.Run(Async Function() As Task
Await DoSomethingAsync()
End Function)
' Continue with other stuff and don't care about DoSomethingAsync()
End Function
Private Async Function DoSomethingAsync() As Task
' Doing long running stuff
End Function

Public Async Function MainThreadAsync() As Task 
DoSomethingAsync()
' Continue with other stuff and don't care about DoSomethingAsync()
End Function
Private Async Sub DoSomethingAsync()
' Doing long running stuff
End Sub

或者有更好的方法吗? 另外,在这方面,c# 和 vb.net 有区别吗?

首先:不要使用async void。我意识到它表达了你想要的语义,但是有一些框架内部如果遇到它就会主动爆炸(这是一个漫长而无趣的故事(,所以:不要进入这种做法。

让我们假设我们有:

private async Task DoSomething() {...}

在这两种情况下,出于这个原因。


这里的主要区别在于,从调用方的角度来看,无法保证DoSomething不会同步运行。所以在这种情况下:

public async task MainThread() {
_ = DoSomething(); // note use of discard here, because we're not awaiting it
}

DoSomething将在主线程上运行至少与第一个await一样远 - 特别是第一个不完整await。好消息是:您只需添加:

await Task.Yield();

作为DoSomething()的第一行,它保证立即返回给调用方(因为本质上Task.Yield总是不完整的(,避免了必须通过Task.Run。在内部,Task.Yield()做一些与Task.Run()非常相似的事情,但它可以跳过一些不必要的部分。

把这一切放在一起 - 如果是我,我会:

public async Task MainThread() {
_ = DoSomething();
// Continue with other stuff and don't care about DoSomething()
}
private async Task DoSomething() {
await Task.Yield();
// Doing long running stuff
}

关于 c#,这种情况:

private async void DoSomething() {
// Doing long running stuff
}
public async task MainThread() {
DoSomething();
// Continue with other stuff and don't care about DoSomething()
}

将运行synchronously,因为可以等待的是任务并且没有创建任何任务。

但是,此代码:

private async task DoSomething() {
// Doing long running stuff
}
public async task MainThread() {
_ = Task.Run(async () => await DoSomething());
// Continue with other stuff and don't care about DoSomething()
}

将在显式创建和启动新任务时异步运行(fire-firget(

是的,有区别。但首先让我们遵循准则并将Async后缀附加到异步方法DoSomethingMainThread

private async Task DoSomethingAsync() {
// Doing long running stuff
}
public async Task MainThreadAsync() {
_ = Task.Run(async () => await DoSomethingAsync());
// Continue with other stuff and don't care about DoSomethingAsync()
}

这可确保DoSomethingAsync从头到尾在ThreadPool线程上运行。主线程甚至不会运行DoSomethingAsync方法的一行。它只会安排一个TaskThreadPool上运行,这是一个以纳秒为单位的微小工作,然后继续执行其他东西。永远不会观察到DoSomethingAsync方法中可能存在的异常。除非您处理TaskScheduler.UnobservedTaskException事件(获取不确定延迟的通知(,或更改 App.config 中的特定设置,或者您正在运行 .NET Framework 4.0。

private async void DoSomething() {
// Doing long running stuff
}
public async Task MainThreadAsync() {
DoSomething();
// Continue with other stuff and don't care about DoSomething()
}

这将开始在主线程上运行DoSomething,并在遇到未完成的可等待await时返回。从概念上讲,您可以说DoSomething由两部分组成,同步部分和异步部分。第一部分将在主线程上运行,第二部分将在ThreadPool线程上运行(因为你的应用是控制台应用,除非手动安装,否则没有SynchronizationContext(。

DoSomethingasync void的,所以DoSomething方法中可能出现的异常将在当前SynchronizationContext上抛出,如果没有安装,则会在ThreadPool上抛出。这意味着在引发AppDomain.UnhandledException事件后,控制台应用将不受控制地崩溃。在某些情况下,这可能正是您想要的。例如,如果您 100% 确定DoSomething永远不会在正常情况下抛出,则立即崩溃可能比让应用在可能损坏的内部状态下继续运行更可取。一般来说,您应该尽量减少即发即弃任务和async void方法的使用,因为它们会使您的程序更加混乱和不可预测。

最新更新