wpf 如何在UI和非UI代码中保证`await`与`ContinueWith`的等效行为

uinbv5nw  于 2023-10-22  发布在  其他
关注(0)|答案(1)|浏览(107)

我正在寻找最简单、最安全的方法来完全保证与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一样?

f4t66c6m

f4t66c6m1#

我不知道你为什么不使用await。使用await的代码比使用旧的ContinueWith方法的代码更容易编写和维护。
也就是说,有几个主要的区别。第一个是await捕获当前上下文;这是SynchronizationContext.CurrentTaskScheduler.Current(或者根本没有上下文,逻辑上等同于TaskScheduler.Default)。await直接在该上下文中调度其延续,而不会将SynchronizationContext Package 到TaskScheduler中。
所以,如果你想使用ContinueWith,那么你可以使用FromCurrentSynchronizationContext,但是已经有了语义上的差异:对于非nullSynchronizationContext.Currentawait延续将具有TaskScheduler.Current等于TaskScheduler.Default,而ContinueWith延续将具有TaskScheduler.Current等于包围SynchronizationContextTaskScheduler示例。也许不是一个大问题,但要注意,许多低级TPL方法隐式地使用TaskScheduler.Current(包括ContinueWith)。
另一个主要区别是ContinueWith没有内置的异步代码理解。因此,如果DoSomethingElse是异步的,那么行为将有所不同。在这种情况下,ContinueWith将返回一个Task<Task>,您应该调用Unwrap来更接近地模拟await的行为。
也有一些小的区别;传递TaskContinuationOptions.DenyChildAttach | TaskContinuationOptions.ExecuteSynchronously应该更接近await行为。您也可以尝试TaskContinuationOptions.HideScheduler,这应该会导致TaskScheduler.Current在延续中成为TaskScheduler.Default
因此,总的来说,这两个应该是 * 大部分 * 等价的:

await DoSomethingAsync();
DoSomethingElseSynchronously();

var continuationTask = DoSomethingAsync()
    .ContinueWith(
        _ => DoSomethingElseSynchronously(),
        CancellationToken.None,
        TaskContinuationOptions.DenyChildAttach | TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.HideScheduler,
        SynchronizationContext.Current == null ? TaskScheduler.Default : TaskScheduler.FromCurrentSynchronizationContext());

或者,如果异步:

await DoSomethingAsync();
await DoSomethingElseAsync();

var continuationTask = DoSomethingAsync()
    .ContinueWith(
        _ => DoSomethingElseAsync(),
        CancellationToken.None,
        TaskContinuationOptions.DenyChildAttach | TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.HideScheduler,
        SynchronizationContext.Current == null ? TaskScheduler.Default : TaskScheduler.FromCurrentSynchronizationContext())
    .Unwrap();

相关问题