任务上下文可以在其第一次等待之前切换吗?

时间:2018-10-05 18:39:41

标签: c# async-await

创建Task是上下文切换点,还是仅在它开始await或进行其他异步操作时创建?

例如,如果我具有以下功能:

async Task Foo()
{
  Console.WriteLine("In Foo");
  await bar();
}

void CallFoo()
{
  var task = Foo();
  Console.WriteLine("Returned from Foo");
  task.Wait();
}

“从Foo返回”是否可以在“ In Foo”之前打印?

2 个答案:

答案 0 :(得分:11)

  

“从Foo返回”是否可以在“ In Foo”之前打印?

在您编写的程序中,不行,这是不可能的。

如果您有运行工作者线程的其他程序,那么将遇到在线程之间排序副作用的所有常见问题。 “异步/等待”没有什么特别的可以解决这些问题。

  

创建Task是上下文切换点,还是仅当它开始等待或执行其他异步操作时?

我们用“上下文切换点”的含义来精确地说。

C#中的方法可以执行以下四项操作之一:

  • 永远奔跑
  • 正常返回
  • 投掷
  • 暂停-我认为您所说的“上下文切换”。

标记为async的方法可以挂起,但是只有在await尚未完成时才挂起。等待已完成的等待不会暂停,但是会抛出。

async方法返回的任务是 hot 。也就是说,异步工作流开始并运行,直到返回,抛出或挂起。可以使用Task构造函数创建一个“冷”任务,该任务直到您调用Start才开始。通常,除非您正在编写自己的任务计划程序,否则您不会这样做。

请注意,这与迭代器块不同。一个常见的错误是:

IEnumerable<int> GetMeSomeInts(int x) 
{
  if (x < 0)
    throw new SomeException();
  for (int i = 0; i < x; i += i)
    yield return i;
}

如果你说

var nums = GetMeSomeInts(-1); // 1
foreach(int num in nums)      // 2
   ...

然后,在第1行上不发生投掷,而在第2行上发生投掷!调用迭代器块时,它们不会运行到第一个yield。它们立即挂起,直到在foreach中进行迭代才执行。请特别注意,如果您在同一程序中同时使用异步和迭代协程。

答案 1 :(得分:6)

来自C# 5.0 language spec

  

10.15.1评估返回任务的异步功能

     

调用返回任务的异步函数会导致生成返回任务类型的实例。这称为异步功能的 返回任务 。该任务最初处于不完整状态。

     

然后对异步函数主体进行评估,直到它被挂起(通过到达await表达式)或终止为止,此时控制权连同返回任务一起返回给调用者。

在问题的示例中,Foo将立即求值,直到它的第一个await为止,然后才将控制权返回给调用者。 “从Foo返回之前”将始终打印“在Foo中”。