在没有等待的情况下使用异步?

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 使用不同的默认值:DenyChildAttachTaskScheduler.DefaultTaskScheduler 特别重要,因为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】:

你还是误会asyncasync 关键字不是的意思是“在另一个线程上运行”。

要将某些代码推送到另一个线程,您需要明确地执行此操作,例如,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() 左右。

如果您调用的函数是一个 void 事件处理程序,那很好。异步调用将在一定程度上(即 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 voidasync Task 之间的唯一区别是异常传播,它只对事件处理程序有意义。我还没有观察到一个具体的例子来说明其他情况。

以上是关于在没有等待的情况下使用异步?的主要内容,如果未能解决你的问题,请参考以下文章

如何在没有等待的情况下调用异步函数?

如何在没有 IdlingResource 的情况下在 Espresso 中等待异步任务

反应等待异步还是重新渲染?

异步等待正确使用

java中同步和异步有什么异同?

同步异步阻塞非阻塞