最初我认为所有延续都在线程池上执行(给定默认同步上下文)。但是,当我使用<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
何时会立即同步调用延续?
答案 0 :(得分:10)
最初我认为所有延续都在线程池上执行(给定默认同步上下文)。但是,当我使用TaskCompletionSource时,情况似乎并非如此。
实际上,使用await
时,大多数延续都是同步执行的。
TaskCompletionSource<T>
会在调用Set*
时同步运行。 Set*
将完成任务和在单个方法调用中发出延续。 (这意味着在持有锁的同时调用Set*
是导致死锁的一种方法。)
我在那里使用奇怪的短语“发出延续”,因为它可能会或可能不会实际执行它们;更多关于此。
TaskCreationOptions.RunContinuationsAsynchronously
标志将告诉TaskCompletionSource<T>
异步发出连续。这打破了任务的完成(仍由Set*
立即完成)从发布延续(仅由Set*
调用触发)。因此,对于RunContinuationsAsynchronously
,Set*
调用只会完成任务;它不会同步执行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)。