异步回调和await的使用

5

关于异步作业的执行顺序,我有一些问题。

我会通过示例来解释我的问题,因为这样更容易理解。

这是一个官方示例,来源于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的回调函数吗?

如果确实如此,那么只有在DoWork1client.GetStringAsync运行时间更长时,DoWork2才不会紧接着client.GetStringAsync完成而立即执行。

我这样说对吗?


是的,当DoWork1()完成时,您开始任务并等待任务。如果它已经完成,DoWork2立即启动。否则,DoWork2将在任务完成后启动。 - Alexander Derck
@AlexanderDerck 那么这不是我期望的那种“回调”。在我看来,一旦其异步函数完成,回调应该立即执行。 - derek
1
只是想澄清一下:即使DoWork1尚未完成,您也希望在getStringTask完成后执行DoWork2?也许可以研究一下Task.ContinueWith方法。 - Hans Kesting
这就是为什么这通常不被称为“回调”的原因。它是相关的,并且在底层可能会使用回调,但你通常不会发现await被描述为回调。 - Damien_The_Unbeliever
@HansKesting 是的,我希望在getStringTask完成后立即执行DoWork2。似乎"await"和"Task.ContinueWith"函数是相同的。请参阅此帖子:https://dev59.com/9Woy5IYBdhLWcg3wHaXt - derek
3个回答

6
如果DoWork1运行时间比client.GetStringAsync长,那么紧接着client.GetStringAsync完成后并不会立即执行DoWork2。一旦达到这个点,await client.GetStrinkAsync已经完成,因为从你的示例来看,它的执行是同步的。一旦client.GetStringAsync完成,那么DoWork2就被设置为执行。
执行流程如下:
  1. GetStringAsync异步启动,它会一直执行直到碰到第一个内部await,然后将控制权交还给AccessTheWebAsync
  2. DoWork1()开始执行,由于是同步的,所有人都在等待它的完成。
  3. client.GetStringAsync被异步地等待完成。 await自然会将执行控制权返回给调用它的方法。
  4. 一旦client.GetStringAsync完成,执行将返回到AccessTheWebAsyncDoWork2()将同步执行。
  5. urlContents.length将被返回。
通常情况下,第一个await关键字之后的所有内容都被设置为该方法的其余部分的延续。

有没有办法让DoWork2client.GetStringAsync完成后立即执行? - derek
@derek 但这正是发生的事情。一旦client.GetStringAsync完成,DoWork2将执行。 - Yuval Itzchakov
DoWork1()需要10秒钟才能完成,而client.GetStringAsync只需要5秒钟。所以当client.GetStringAsync完成时,DoWork1()仍在运行,因此我们需要等待DoWork1()完成,对吗?然后5秒钟后,DoWork1()完成。只有在这个时候,DoWork2()才能开始工作。如果我错了,请纠正我。 - derek
@derek 不是的,DoWork1()同步的。在它完成之前不会有任何东西运行。如果 GetStringAsync 需要 5 秒钟,那么一旦方法完成 DoWork1GetStringAsync 就已经完成了,执行将同步继续到 DoWork2 - Yuval Itzchakov
在前5秒内,GetStringAsyncDoWork1()是否会在不同的线程中同时运行? - derek
@derek 是的,你说得对,我忘记了 GetStringAsync 是在没有被等待的情况下执行的。在 await GetStringAsync 执行之前,我们必须等待 DoWork1 完成。 - Yuval Itzchakov

5

Yuval的回答是正确的。

针对你的后续问题:

有没有一种方法可以在客户端完成GetStringAsync后立即执行DoWork2?

可能。这取决于代码的上下文。显然,无法让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;
}

-1

输入:

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

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


1
当您正确地使用await异步方法时,说getStringTask会阻塞程序的执行是完全相反的。 - Yuval Itzchakov
1
同意:使用'suspend'或'suspend the execution'或将AccessTheWebAsync()的剩余部分分配为'getStringTask'的延续是更正确的用法。Block是不正确的(已更新)。 - Ahmad
1
不,任务并没有立即运行。await 的作用只是获取另一段代码已经启动的任务(或其他可等待的对象),如果任务已经完成则什么也不做,否则暂停当前方法的执行直到任务完成。但是需要再次强调的是,await 不会启动任何东西。 - Damien_The_Unbeliever

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接