_ = Task.Run与异步void | Task.Run与异步子

时间:2020-05-21 08:41:53

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

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

这样做之间有什么区别

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

并且这样做?

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

VB.Net:

Private Async Function DoSomethingAsync() As Task
    ' Doing long running stuff
End Function

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

vs

Private Async Sub DoSomethingAsync()
    ' Doing long running stuff
End Sub

Public Async Function MainThreadAsync() As Task 
    DoSomethingAsync()
    ' Continue with other stuff and don't care about DoSomethingAsync()
}

还是有更好的方法? 另外,在这方面c#和vb.net之间有什么区别吗?

3 个答案:

答案 0 :(得分:3)

首先:请勿使用 async void。我意识到它表达了您想要的语义,但是如果遇到某些框架内部元素,它们会主动爆炸(这是一个漫长而无趣的故事),因此:不要参与该练习。

让我们假装我们有

private async Task DoSomething() {...}
出于这种原因,在两种情况下


这里的主要区别从呼叫者的角度来看 没有{em>保证不会DoSomething同步运行。因此,在这种情况下:

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

DoSomething将至少在主线程 上运行,直到第一个await-具体来说,第一个不完整 {{1} }。好消息是:您可以添加:

await

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

将所有内容放在一起-如果是我,我将拥有:

Task.Yield()

答案 1 :(得分:1)

关于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,因为可以等待的是Task,并且没有创建任何Task。

但是这段代码:

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)

答案 2 :(得分:1)

是的,有区别。但是首先让我们遵循guidelines并将后缀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否则没有install one manually)。 / p>

DoSomethingasync void,因此DoSomething方法中可能出现的异常将在当前的SynchronizationContextThreadPool中抛出(如果存在)没有安装。这意味着您的控制台应用在引发AppDomain.UnhandledException事件后将无法控制地崩溃。在某些情况下,这可能正是您想要的。例如,如果您100%确定DoSomething绝对不要在正常情况下抛出,那么立即崩溃可能比让应用程序在内部状态可能已损坏的情况下继续运行更为可取。通常,尽管您应该尝试尽量减少即弃任务和异步无效方法的使用,因为它们会使程序更加混乱和不可预测。