在控制台应用程序中。我需要从我的主线程加载一些长时间运行的代码(网络内容,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
后缀附加到异步方法DoSomething
和MainThread
:
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
方法的一行。它只会安排一个Task
在ThreadPool
上运行,这是一个以纳秒为单位的微小工作,然后继续执行其他东西。永远不会观察到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
(。
DoSomething
是async void
的,所以DoSomething
方法中可能出现的异常将在当前SynchronizationContext
上抛出,如果没有安装,则会在ThreadPool
上抛出。这意味着在引发AppDomain.UnhandledException
事件后,控制台应用将不受控制地崩溃。在某些情况下,这可能正是您想要的。例如,如果您 100% 确定DoSomething
永远不会在正常情况下抛出,则立即崩溃可能比让应用在可能损坏的内部状态下继续运行更可取。一般来说,您应该尽量减少即发即弃任务和async void
方法的使用,因为它们会使您的程序更加混乱和不可预测。