【问题标题】: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:不是实现细节。它已明确指定 - 并且必须如此,因此它的行为始终是可预测且可靠的(一旦您了解了机制)。

标签: c# task-parallel-library async-await c#-5.0


【解决方案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(请注意,只使用两个线程,所以它也很敏感),而任务库将完美地管理它。我还遇到了一个问题,我已经将几个线程排队,直到它们都完成后才到达回调,在一种线程雪崩中。
  • 如果 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 之间的唯一区别是异常传播,它只对事件处理程序有意义。我还没有观察到一个具体的例子来说明其他情况。
    猜你喜欢
    • 2013-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-18
    • 2018-06-16
    相关资源
    最近更新 更多