当前位置: 首页 > 知识库问答 >
问题:

我们应该在调用异步回调的库中使用配置等待(false)吗?

扶绍辉
2023-03-14

在C#中使用await/async时,有很多关于何时使用< code > configurewait(false)的指导原则。

一般建议在库代码中使用ConfigureAwait(false),因为它很少依赖于同步上下文。

然而,假设我们正在编写一些非常通用的实用程序代码,它接受一个函数作为输入。一个简单的例子是下面的(不完整的)函数组合子,它使简单的基于任务的操作变得更容易:

地图:

public static async Task<TResult> Map<T, TResult>(this Task<T> task, Func<T, TResult> mapping)
{
    return mapping(await task);
}

平面图:

public static async Task<TResult> FlatMap<T, TResult>(this Task<T> task, Func<T, Task<TResult>> mapping)
{
    return await mapping(await task);
}

问题是,在这种情况下,我们应该使用ConfigreAetc(false)吗?我不确定上下文捕获是如何工作的。闭包。

一方面,如果以函数方式使用组合符,则不需要同步上下文。另一方面,人们可能会滥用API,在提供的函数中执行上下文相关的操作。

一种选择是为每个场景使用单独的方法(MapMapWithContextCapture,或类似的方法),但感觉很难看。

另一个选项可能是添加选项,以映射/平面图从和到配置任务

有没有一种好方法可以将责任转移给调用者,这样实现的库就不需要对所提供的映射函数中是否需要上下文做任何假设?

或者,如果没有各种假设,异步方法不能很好地组合,这只是一个事实吗?

只想澄清几件事:

  1. 问题确实存在。当您在实用程序函数内部执行“回调”时,添加配置等待(false)将导致空同步。上下文。
  2. 主要问题是我们应该如何处理这种情况。我们应该忽略有人可能想要使用同步的事实吗?上下文,或者除了添加一些重载、标志等之外,有没有好的方法将责任转移给调用者?

正如一些回答提到的,可以给方法添加一个bool-flag,但是在我看来,这也不太好,因为它必须通过API传播(因为有更多的“实用”函数,取决于上面显示的那些)。


共有3个答案

华心思
2023-03-14

我认为这里真正的问题来自于这样一个事实,即在实际操作<code>任务

没有真正的理由将这些操作作为容器复制给任务,而不是将它们保留在任务结果中。

这样,您就不需要决定如何在实用程序方法中等待此任务,因为该决定保留在消费者代码中。

如果Map改为按以下方式实现:

public static TResult Map<T, TResult>(this T value, Func<T, TResult> mapping)
{
    return mapping(value);
}

您可以轻松地使用它与或不Task.ConfigureAwait相应:

var result = await task.ConfigureAwait(false)
var mapped = result.Map(result => Foo(result));

Map这里只是一个例子。关键是您在这里操作什么。如果您正在操作任务,您不应该等待它并将结果传递给消费者委托,您可以简单地添加一些异步逻辑,您的调用者可以选择是否使用Task.ConfigureAwait。如果您对结果进行操作,则无需担心任务。

您可以将布尔值传递给这些方法中的每一个,以表示是否要继续捕获上下文(或者更可靠地传递选项<code>enumMap(或其等价物)没有任何关系。

欧阳飞
2023-03-14

问题是,在这种情况下我们应该使用configurewait(false)吗?

是的,你应该这样做。如果正在等待的内部任务是上下文感知的,并且确实使用了给定的同步上下文,那么即使调用它的人正在使用 ConfigureAwait(false),它仍然能够捕获它。不要忘记,在忽略上下文时,您是在更高级别的调用中执行此操作的,而不是在提供的委托中执行此操作。如果需要,在任务内执行的委托需要具有上下文感知能力。

您,调用者,对上下文没有兴趣,因此使用Configawait(false)调用它是绝对可以的。这有效地执行了所需的操作,它将内部委托是否包含同步上下文的选择权留给了 Map 方法的调用方。

编辑:

需要注意的重要一点是,一旦您使用了配置等待(false),之后的任何方法执行都将在任意线程池线程上进行。

@i3arnon建议的一个好主意是接受一个可选的bool标志,指示是否需要上下文。虽然有点难看,但这将是一个很好的解决方案。

宦博雅
2023-03-14
匿名用户

当你说<code>等待任务时。ConfigureAwait(false)您转换到线程池,导致<code>映射

await Map(0, i => { myTextBox.Text = i.ToString(); return 0; }); //contrived...

然后,这将在以下 Map 实现下崩溃:

var result = await task.ConfigureAwait(false);
return await mapper(result);

但不是在这里:

var result = await task/*.ConfigureAwait(false)*/;
...

更可怕的是:

var result = await task.ConfigureAwait(new Random().Next() % 2 == 0);
...

掷硬币关于同步上下文!这看起来很有趣,但它并不像看起来那么荒谬。一个更现实的例子是:

var result =
  someConfigFlag ? await GetSomeValue<T>() :
  await task.ConfigureAwait(false);

因此,根据某些外部状态,运行方法的其余部分的同步上下文可能会更改。

这也可能发生在非常简单的代码中,例如:

await someTask.ConfigureAwait(false);

如果 someTask 在等待它时已经完成,则不会切换上下文(出于性能原因,这很好)。如果需要切换,则该方法的其余部分将在线程池上恢复。

这种非确定性是wait设计的弱点。这是以性能为名的权衡。

这里最令人烦恼的问题是调用API时不清楚会发生什么。这令人困惑并导致错误。

怎么办?

备选方案1:您可以认为最好通过始终使用< code>task来确保确定性行为。configurewait(false)。

lambda必须确保它在正确的上下文中运行:

var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext;
Map(..., async x => await Task.Factory.StartNew(
        () => { /*access UI*/ },
        CancellationToken.None, TaskCreationOptions.None, uiScheduler));

最好在实用方法中隐藏其中的一些。

备选方案2:您也可以认为< code>Map函数应该与同步上下文无关。它应该不去管它。然后上下文将流入lambda。当然,仅仅同步上下文的存在就可能改变< code>Map的行为(不是在这种特殊情况下,而是在一般情况下)。所以< code>Map必须被设计来处理这个问题。

备选方案 3:可以将布尔参数注入 Map,以指定是否对上下文进行流动。这将使行为明确。这是合理的API设计,但它使API混乱。关注具有同步上下文问题的 Map 等基本 API 似乎不合适。

走哪条路线?我觉得要看具体情况。例如,如果< code>Map是一个UI帮助器函数,那么流动上下文是有意义的。如果它是一个库函数(如重试助手),我不确定。我认为所有的选择都有道理。通常,建议在所有库代码中应用< code > configurewait(false)。我们应该在调用用户回调的情况下破例吗?如果我们已经离开了正确的上下文,例如:

void LibraryFunctionAsync(Func<Task> callback)
{
    await SomethingAsync().ConfigureAwait(false); //Drops the context (non-deterministically)
    await callback(); //Cannot flow context.
}

所以不幸的是,没有简单的答案。

 类似资料:
  • 问题内容: 我有一个在另一个类中启动异步任务,然后应该等待结果。 问题是方法将在方法运行完成后立即完成,对吗? 这意味着,通常,在启动异步任务后,它将立即关闭,并且不再在那里接收结果。 如何使以上代码段正常工作?开始任务后,我已经尝试放入(任意持续时间)。似乎可以正常工作。 但这绝对不是一个干净的解决方案。也许甚至有一些严重的问题。 有更好的解决方案吗? 问题答案: 使用标准类代替,从回调启动异步

  • 我使用Java中的创建一个线程池,每个线程在其中执行一些异步任务/调用另一个服务。我不想等待回复,但无论何时回复都会返回。 这样做将等待第一个请求完成,然后返回结果。问题是,第二个请求必须等待第一个请求的响应返回,并且只有在这之后才会被处理。如果我使用CompletableFuture的方法,也会发生同样的情况,因为我还必须使用来获取响应。 我希望所有的请求都经过检查,并在收到时返回回复。这是可以

  • 本文向大家介绍承诺回调和异步/等待,包括了承诺回调和异步/等待的使用技巧和注意事项,需要的朋友参考一下 首先,我们必须了解两个主要概念 同步编程 异步编程 同步编程 它等待每个语句完成执行,然后再转到下一条语句。 如果语句不相互依赖,但是由于它们在队列中,它们仍在等待执行,则此方法可能会减慢应用程序的速度。 异步编程 在移动到下一条语句之前,它不等待当前语句完成执行。例如,调用Web服务并使用Ja

  • 我的JavaScript代码如下所示: 完成所有这些异步调用后,我想计算所有数组的最小值。 我怎么能等到他们所有人呢? 我现在唯一的想法是有一个布尔数组叫做done,并在第i个回调函数中将done[i]设置为true,然后说while(not all are done){} edit:我想一个可能的,但很难看的解决方案是在每个回调中编辑done数组,然后在每个回调中设置了所有其他done的情况下调

  • 问题内容: 我知道这个问题以前曾被问过,但是所有解决方案都不适合我。 我有一个将参数发送到API的函数,并以列表的形式返回数据。我有一个UITableView设置为使用该列表,但是它在列表分配给变量之前运行。 码: 如果不立即将其作为重复投票,我将不胜感激,这是我尝试的方法。 派遣组 信号量计时 运行变量 其中包括= self和= self 。 编辑:要求提取项目, 问题答案: 您不能-也不应该-

  • 问题内容: 据我了解,在ES7 /ES2016中,将多个in放在代码中的工作方式类似于带有promise的链接,这意味着它们将一个接一个地执行而不是并行执行。因此,例如,我们有以下代码: 我是否正确理解仅在完成时才会调用?并行调用它们的最优雅方式是什么? 我想在Node中使用它,所以也许有一个异步库解决方案? 编辑:我对这个问题提供的解决方案不满意:减速是由于异步生成器中非并行等待Promise的