我正在寻找最简单、最安全的方法来完全保证与await
相同的行为,但使用Task.ContinueWith
,最具体的是确保如果原始调用者在UI线程中,则继续发生在UI线程上,否则它应该在任何线程上继续。我目前的方法在某些情况下抛出异常,我看不到一个明显的方法来提前防止。
我之前的理解是,这段代码:
await DoSomethingAsync();
DoSomethingElse();
这个代码:
DoSomethingAsync().ContinueWith(
t => DoSomethingElse(),
TaskScheduler.FromCurrentSynchronizationContext());
应该是等价的,如果DoSomethingElse()
是一个UI线程,那么DoSomethingElse()
将在原始调用线程上执行,否则延续线程将是未定义的。
但是,当没有定义UI线程或同步器时,TaskScheduler.FromCurrentSynchronizationContext()
至少在某些上下文中会失败。这至少发生在Mono WASM项目中。documentation还提到,如果“当前的SynchronizationContext可能无法用作TaskContext”,则该方法可能会失败,但它没有说明如何在尝试将其与ContinueWith
一起使用之前检查是否是这种情况。
由于这些代码将在许多不同的消费者之间共享,从WPF到Mono WASM和其他人,我需要一个适合所有人的解决方案,并且不需要消费者指定它是否在UI上下文中。
写第二个块的最简单的方法是什么,这样当实际上有一个有效的同步上下文(如在WPF应用程序中)时,继续在UI线程上发生-但如果没有异常,也不会抛出异常-就像await
一样?
1条答案
按热度按时间f4t66c6m1#
我不知道你为什么不使用
await
。使用await
的代码比使用旧的ContinueWith
方法的代码更容易编写和维护。也就是说,有几个主要的区别。第一个是
await
捕获当前上下文;这是SynchronizationContext.Current
或TaskScheduler.Current
(或者根本没有上下文,逻辑上等同于TaskScheduler.Default
)。await
直接在该上下文中调度其延续,而不会将SynchronizationContext
Package 到TaskScheduler
中。所以,如果你想使用
ContinueWith
,那么你可以使用FromCurrentSynchronizationContext
,但是已经有了语义上的差异:对于非null
SynchronizationContext.Current
,await
延续将具有TaskScheduler.Current
等于TaskScheduler.Default
,而ContinueWith
延续将具有TaskScheduler.Current
等于包围SynchronizationContext
的TaskScheduler
示例。也许不是一个大问题,但要注意,许多低级TPL方法隐式地使用TaskScheduler.Current
(包括ContinueWith
)。另一个主要区别是
ContinueWith
没有内置的异步代码理解。因此,如果DoSomethingElse
是异步的,那么行为将有所不同。在这种情况下,ContinueWith
将返回一个Task<Task>
,您应该调用Unwrap
来更接近地模拟await
的行为。也有一些小的区别;传递
TaskContinuationOptions.DenyChildAttach | TaskContinuationOptions.ExecuteSynchronously
应该更接近await
行为。您也可以尝试TaskContinuationOptions.HideScheduler
,这应该会导致TaskScheduler.Current
在延续中成为TaskScheduler.Default
。因此,总的来说,这两个应该是 * 大部分 * 等价的:
或者,如果异步: