【问题标题】:Callback with async and await使用 async 和 await 回调
【发布时间】:2016-09-02 06:53:32
【问题描述】:

我对异步作业的执行顺序有疑问。

我会用例子来问我的问题,因为它更容易理解。

这是来自https://msdn.microsoft.com/en-us/library/mt674882.aspx 的官方示例,带有一些扭曲。

async Task<int> AccessTheWebAsync()
{ 
    HttpClient client = new HttpClient();

    //async operation:
    Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");

    // You can do work here that doesn't rely on the string from GetStringAsync.
    DoWork1();

    // The await operator suspends AccessTheWebAsync.
    string urlContents = await getStringTask;

    DoWork2();

    return urlContents.Length;
}

我可以说DoWork2client.GetStringAsync 的回调吗?

如果是这样,DoWork2 不会在 client.GetStringAsync 完成后立即执行 IF DoWork1 运行时间比 client.GetStringAsync 长。

我在这里吗?

【问题讨论】:

  • 是的,您开始任务,当DoWork1() 完成时,您将等待任务。如果已经完成,DoWork2 会立即开始。如果没有,DoWork2 在任务完成时启动。
  • @AlexanderDerck 那么这不是我所期望的那种“回调”。在我看来,一旦异步函数完成,回调应该立即执行。
  • 只是为了澄清:即使 DoWork1 尚未完成,您是否希望 DoWork2 执行(在 getStringTask 完成时)?也许看看Task.ContinueWith 方法。
  • 这就是为什么这通常不被称为“回调”。它是相关的,并且在幕后可能会使用回调,但您通常不会发现 await 被描述为回调。
  • @HansKesting 是的,我希望在 getStringTask 完成后立即执行 DoWork2。似乎“等待”和“Task.ContinueWith”功能相同。看到这个帖子:stackoverflow.com/questions/8767218/…

标签: c# asynchronous async-await c#-6.0


【解决方案1】:

DoWork2 不会在完成后立即执行 client.GetStringAsync 如果DoWork1 运行时间比 client.GetStringAsync

一旦你达到了await client.GetStrinkAsync 的那个点,DoWork1() 就已经完成了,从你的例子来看,它的执行是同步的。一旦client.GetStringAsync 完成,DoWork2 就会被设置为执行。

执行流程为:

  1. GetStringAsync 异步启动。它会一直执行,直到遇到第一个内部await,然后将控制权交还给AccessTheWebAsync
  2. DoWork1() 开始。因为它是同步的,所以每个人都在等待它的完成
  3. client.GetStringAsync 异步等待完成。 await 自然会将执行控制权交给它的调用者。
  4. 一旦client.GetStringAsync完成,执行将返回AccessTheWebAsyncDoWork2()将同步执行。
  5. urlContents.length 将被退回。

通常,第一个 await 关键字之后的所有内容都设置为方法其余部分的延续。

【讨论】:

  • 有没有办法让DoWork2client.GetStringAsync完成后立即执行?
  • @derek 但这正是发生的事情。一旦client.GetStringAsync 完成,DoWork2 将执行。
  • DoWork1() 需要 10 秒才能完成,而 client.GetStringAsync 需要 5 秒。所以当client.GetStringAsync 完成时,DoWork1() 仍在运行,所以我们需要等待DoWork1() 完成,对吧?然后 5 秒后,DoWork1() 完成。只有在这个时候,DoWork2() 才能开始工作。如果我错了,请纠正我。
  • @derek No. DoWork1()同步的。在它完成之前什么都不会运行。如果GetStringAsync 需要5 秒,那么一旦方法完成DoWork1GetStringAsync 就已经完成,执行将同步继续到DoWork2
  • 前 5 秒,GetStringAsync 不是在不同线程中同时运行 DoWork1() 吗?
【解决方案2】:

Yuval's answer 是正确的。

回答您的后续问题:

有没有办法让 DoWork2 在 client.GetStringAsync 完成后立即执行?

可能。这取决于此代码的上下文。 DoWork1DoWork2 不可能同时在 UI 线程上运行(显然,单个线程一次只能做一件事)。但是如果这是从线程池上下文中调用的,那么当然可以同时运行它们。

但是,您应该将思维从“回调”转变为“操作”。也就是说,不要将其视为DoWork2GetStringAsync 的“回调”。相反,可以这样想:我有一个操作GetStringAsync,我有一个操作DoWork2;我需要一个新的操作,一个然后另一个。

一旦你这样转变你的想法,解决办法就是为新的操作写一个方法:

async Task<string> GetStringAndDoWork2Async()
{
  HttpClient client = new HttpClient();
  var result = await client.GetStringAsync("http://msdn.microsoft.com");
  DoWork2();
  return result;
}

现在您可以在更高级别的方法中使用该新操作:

async Task<int> AccessTheWebAsync()
{
  var task = GetStringAndDoWork2Async();

  DoWork1();

  string urlContents = await task;

  return urlContents.Length;
}

【讨论】:

    【解决方案3】:

    在:

    //async operation:
    Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
    

    您正在创建一个运行异步操作并立即返回给调用者的任务,接下来DoWork1() 同步执行,一旦完成string urlContents = await getStringTask; 将暂停该方法的继续,直到等待的任务完成;完成后,程序将继续使用DoWork2()

    【讨论】:

    • getStringTask 块与正确await 异步方法时发生的情况完全相反。
    • 同意:“暂停”或“暂停执行”或将 AccessTheWebAsync() 的剩余部分分配为“getStringTask”的延续是更正确的用法。区块不正确(已更新)
    • 不,任务会立即运行。 await 所做的只是接受一个任务(或其他可等待的),该任务(或其他可等待的)已经启动了其他一些代码,它要么什么都不做(如果任务已经完成),要么暂停当前方法的执行,直到任务完成。但是,再次强调一下,await 并没有开始任何事情。
    猜你喜欢
    • 2022-11-18
    • 1970-01-01
    • 1970-01-01
    • 2016-02-02
    • 1970-01-01
    • 1970-01-01
    • 2019-02-17
    • 1970-01-01
    • 2017-11-20
    相关资源
    最近更新 更多