TaskCompletionSource.SetResult()在哪种情况下同步运行continuation?

时间:2016-09-02 16:02:33

标签: c# multithreading asynchronous async-await

最初我认为所有延续都在线程池上执行(给定默认同步上下文)。但是,当我使用<uses-sdk android:minSdkVersion="INSERT_YOUR_DESIRED_minSdkVersion_HERE" tools:overrideLibrary="com.paypal.android.sdk.payments"/> 时,情况似乎并非如此。

我的代码看起来像这样:

TaskCompletionSource

Task<int> Foo() { _tcs = new TaskCompletionSource<int>(); return _tcs.Task; } async void Bar() { Console.WriteLine(Thread.Current.ManagedThreadId); Console.WriteLine($"{Thread.Current.ManagedThreadId} - {await Foo()}"); } 在特定线程上被调用,Bar在一段时间内保持未设置状态,这意味着返回的任务TaskCompletionSource。然后一段时间后,同一个线程将继续调用IsComplete = false,根据我的理解,应该在线程池上运行延续。

但我在我的应用程序中观察到的是,运行continuation的线程实际上仍然是同一个线程,就好像在调用_tcs.SetResult(x)时同步调用continuation一样。

我甚至尝试在SetResult上设置一个断点并踩过它(并在延续中有一个断点),这反过来又会继续同步调用延续。

SetResult何时会立即同步调用延续?

2 个答案:

答案 0 :(得分:10)

  

最初我认为所有延续都在线程池上执行(给定默认同步上下文)。但是,当我使用TaskCompletionSource时,情况似乎并非如此。

实际上,使用await时,大多数延续都是同步执行的。

马克的答案很棒;我只是想更详细一点......

默认情况下,

TaskCompletionSource<T>会在调用Set*时同步运行。 Set*将完成任务在单个方法调用中发出延续。 (这意味着在持有锁的同时调用Set*是导致死锁的一种方法。)

我在那里使用奇怪的短语“发出延续”,因为它可能会或可能不会实际执行它们;更多关于此。

TaskCreationOptions.RunContinuationsAsynchronously标志将告诉TaskCompletionSource<T>异步发出连续。这打破了任务的完成(仍由Set*立即完成)从发布延续(仅由Set*调用触发)。因此,对于RunContinuationsAsynchronouslySet*调用只会完成任务;它不会同步执行continuation。 (这意味着在持有锁的同时调用Set*是安全的。)

但回到默认情况下,它同步发出延续。

每个延续都有一个标志;默认情况下,继续执行异步,但可以通过TaskContinuationOptions.ExecuteSynchronously使其同步。 (请注意await does use this flag - 链接到我的博客;从技术上讲,这是一个实现细节,并没有正式记录。)

但是,即使指定了ExecuteSynchronously,也有a number of situations where the continuation is not executed synchronously

  • 如果有一个TaskScheduler与延续相关联,则该调度程序可以选择拒绝当前线程,在这种情况下,任务排队TaskScheduler而不是同步执行。
  • 如果当前线程正在中止,则该任务将在其他位置排队。
  • 如果当前线程的堆栈太深,则该任务将在其他位置排队。 (这只是一种启发式方法,并不能保证避免使用StackOverflowException)。

这是一个很好的条件,但通过简单的控制台应用测试,他们都满足了:

  • TaskCompletionSource<T>未指定RunContinuationsAsynchronously
  • 延续(await)确实指定了ExecuteSynchronously
  • 继续没有指定TaskScheduler
  • 目标线程能够执行延续(不中止;堆栈正常)。

作为一般规则,我会说TaskCompletionSource<T>的任何用法都应指定TaskCreationOptions.RunContinuationsAsynchronously。就个人而言,我认为语义更合适,并且不那么令人惊讶。

答案 1 :(得分:8)

SetResult 通常同步从TCS运行延续。如果您在创建TCS(.NET 4.6中的新增功能)时显式传递TaskContinuationOptions.RunContinuationsAsynchronously标志,则主要例外。它异步运行的另一种情况是它是否认为当前线程注定失败。

这非常重要,因为如果你不小心,你最终可能会调用代码来控制一个本打算做其他工作的线程(比如:处理套接字IO)。