在没有等待的情况下使用异步?
Posted
技术标签:
【中文标题】在没有等待的情况下使用异步?【英文标题】:Using async without await? 【发布时间】:2013-07-22 06:27:12 【问题描述】:考虑Using async without await。
认为您可能误解了异步的作用。警告是 完全正确:如果您将方法标记为异步但不使用 await 任何地方,那么您的方法将不会是异步的。如果你叫它,所有 方法里面的代码会同步执行。
我想编写一个应该运行异步但不需要使用等待的方法。例如在使用线程时
public async Task PushCallAsync(CallNotificationInfo callNotificationInfo)
Logger.LogInfo("Pushing new call 0 with 1 id".Fill(callNotificationInfo.CallerId,
我想打电话给PushCallAsync
并运行异步并且不想使用等待。
我可以在 C# 中使用 async 而不使用 await 吗?
【问题讨论】:
好吧。这完全取决于您的设计问题,在这里。你希望“异步”做什么? 如果你不打算等待这个方法,为什么还要声明它是异步的呢?您引用的文字很好地总结了 IMO。 我想你直接用Task.Run
就好了。 async
/await
并不是真正的多线程机制,实际上我认为运行时在尽可能少的线程上执行操作。它主要是关于编译器自动将您的代码转换为连续传递样式,因此您仅在绝对必要时等待结果(或完成)后台操作。如果您不需要等待(或await
)呼叫结果,这不是您正在寻找的语言功能。
@millimoose:async
与线程交互的方式有点复杂,并且可以轻松覆盖默认行为。 async
不是多线程机制,也不总是在单线程上运行。 I have a blog post that summarizes how async
schedules its continuations.
@millimoose:不是实现细节。它已明确指定 - 并且必须如此,因此它的行为始终是可预测且可靠的(一旦您了解了机制)。
【参考方案1】:
如果您的 Logger.LogInfo
已经是异步的,这就足够了:
public void PushCallAsync(CallNotificationInfo callNotificationInfo)
Logger.LogInfo("Pushing new call 0 with 1 id".Fill(callNotificationInfo.CallerId,
如果不是直接异步启动而不等待它
public void PushCallAsync(CallNotificationInfo callNotificationInfo)
Task.Run(() => Logger.LogInfo("Pushing new call 0 with 1 id".Fill(callNotificationInfo.CallerId));
【讨论】:
我读过,在我看来更像是风格上的差异,而不是行为上的实际差异,不是吗? 没有。Task.Run
使用不同的默认值:DenyChildAttach
和 TaskScheduler.Default
。 TaskScheduler
特别重要,因为StartNew
默认使用TaskScheduler.Current
,这使得它可以根据调用者的上下文以不同的方式调度委托。这让很多人感到困惑,以至于许多团队都采用了不允许StartNew
的代码规则,除非指定了TaskScheduler
。
不知道,谢谢提供信息。将代码更改为 Task.Run
按照惯例,除非它具有“async”关键字,否则不应将方法命名为“Async”。【参考方案2】:
如果 Logger.LogInfo 是一个同步方法,那么整个调用无论如何都是同步的。如果您只想在单独的线程中执行代码,那么异步不是这项工作的工具。改用线程池试试:
ThreadPool.QueueUserWorkItem( foo => PushCallAsync(callNotificationInfo) );
【讨论】:
支持并行任务库,而不是直接调用线程池......这可能会产生相当不可预见的后果,具体取决于平台(WinRT、Win、WP8 等)。在我的经验中。 =) 你让我感兴趣......你能否提供更多关于可能产生的后果的细节? 好吧。我已经成功地使用QueueUserWorkItem
完全阻塞了几个设备上的 CPU(请注意,只使用两个线程,所以它也很敏感),而任务库将完美地管理它。我还遇到了一个问题,我已经将几个线程排队,直到它们都完成后才到达回调,在一种线程雪崩中。
考虑this question and its answers, and further reading。
如果 Logger.LogInfo 不是同步方法 你不知道如果 Logger.LogInfo is 是同步方法??【参考方案3】:
你还是误会async
。 async
关键字不是的意思是“在另一个线程上运行”。
要将某些代码推送到另一个线程,您需要明确地执行此操作,例如,Task.Run
:
await Task.Run(() => Logger.LogInfo("Pushing new call 0 with 1 id".Fill(callNotificationInfo.CallerId));
我有一个async
/await
intro post,您可能会觉得有帮助。
【讨论】:
【参考方案4】:你误会async
。
它实际上只是告诉编译器传播它在后台为您执行的控制流的反转。这样整个方法堆栈就被标记为异步。
您实际想要做什么取决于您的问题。 (让我们考虑您的调用 Logger.LogInfo(..)
是一个 async
方法,因为它最终会调用 File.WriteAsync() 左右。
Logger.LogInfo(..)
完成时,您必须采取预防措施。当您的方法以某种方式在调用堆栈的中间 时,就会出现这种情况。然后Logger.LogInfo(..)
通常会返回Task
,您可以等待。但要小心调用 task.Wait() 因为它会死锁你的 GUI 线程。而是使用等待或返回任务(然后您可以省略异步):
public void PushCallAsync(CallNotificationInfo callNotificationInfo)
return Logger.LogInfo("Pushing new call 0 with 1 id".Fill(callNotificationInfo.CallerId);
或
public async void PushCallAsync(CallNotificationInfo callNotificationInfo)
await Logger.LogInfo("Pushing new call 0 with 1 id".Fill(callNotificationInfo.CallerId);
【讨论】:
你不应该使用async void
,除非它是一个事件处理程序。这是唯一可接受的时间,也是制作异步事件处理程序的唯一正确方法。如果你的同步方法是void
,应该是async Task
。这是由于异常处理,其中事件处理程序将在SynchronizationContext
(类似于同步事件)而不是调用者上抛出。如果可能,异步事件处理程序应该是一个精简的私有包装器和 async Task
函数(因此可以对其进行测试或重用)。
@GraemeWicksted 你当然是对的,我支持“不要做 async void”,但为了理解这是我需要的 - 最终你通常会在某个地方有 async void
我不确定我是否理解您的回复。在这里如何证明是合理的?看起来它对我来说应该是async Task
,因为它不是事件处理程序。请记住:async Task
本质上是async Task<void>
,但后者根本不存在。 async void
和 async Task
之间的唯一区别是异常传播,它只对事件处理程序有意义。我还没有观察到一个具体的例子来说明其他情况。以上是关于在没有等待的情况下使用异步?的主要内容,如果未能解决你的问题,请参考以下文章