2016-09-02 40 views
7

最初我以为所有延续都在线程池上执行(给定一个默认的同步上下文)。但是,当我使用TaskCompletionSource时,似乎不是这种情况。TaskCompletionSource.SetResult()在哪种情况下同步运行延续?

我的代码看起来是这样的:

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。然后过了一段时间,同一个线程会继续调用_tcs.SetResult(x),根据我的理解,它应该在线程池上运行延续。

但我在我的应用程序中观察到,运行延续的线程实际上仍然是相同的线程,就好像继续被同步调用一样,因为SetResult被调用。

我甚至试着在SetResult上设置一个断点,并在它上面跳过(并且在继续中有一个断点),然后实际上继续同步地调用continuation。

到底什么时候SetResult()立即同步调用延续?

+0

如果你提供一个[mcve]而不是它的* half *,这将有所帮助,所以我们可以自己尝试一下... –

回答

4

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

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

+0

顺便说一句:当你在一个线程上调用SetResult是不是原来的预先呼叫者?不应该因为默认的'ConfigureAwait(...)'继续尝试去原来的调用者线程? – Petrroll

+0

@Petrroll如果函数被调用时没有'SyncronisationContext.Current',它将没有原始线程去。 –

+0

如果它有一些明确的SyncContext? – Petrroll

5

最初我以为所有延续都是在线程池上执行的(给定一个默认的同步上下文)。但是,当我使用TaskCompletionSource时,这似乎不是这种情况。

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

Marc的答案很棒;我只是想进一步详细...

TaskCompletionSource<T>默认情况下将调用Set*时同步操作。 Set*将完成任务在单个方法调用中发出延续。 (这意味着在锁定时调用Set*是死锁的秘诀。)

我使用奇怪的短语“发布延续”,因为它可能会或可能不会实际执行它们;稍后更多。

TaskCreationOptions.RunContinuationsAsynchronously标志会告诉TaskCompletionSource<T>异步地发出延续。这将从继续发布(这只由Set*调用触发)中分解完成任务(立即通过Set*完成)。所以RunContinuationsAsynchronouslySet*调用将只完成任务;它不会同步执行延续。 (这意味着在锁定时调用Set*是安全的。)

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

每延续有一个标志;默认情况下,异步执行,但可以通过TaskContinuationOptions.ExecuteSynchronously进行同步。 (请注意,awaitdoes 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。就我个人而言,我认为这个标志的语义更合适,更不令人意外。