【问题标题】:What is a safe way to wait synchronously with cancellation?与取消同步等待的安全方法是什么?
【发布时间】:2020-05-15 16:10:22
【问题描述】:

我正在编写一个 .NET Standard 2.0 库,该库将具有相同功能的同步和异步版本,其中一项功能需要延迟取消支持。

我正在尝试想出一种方法来等待特定的时间量,这种方法在所有情况下都不会出现死锁或其他问题。一个担心是waiting synchronously on async methods can cause deadlocks

考虑这个类,你应该如何实现Wait 以使其在任何地方都安全?调用WaitAsync不是必须的,等待可以完全分开实现。

class Waiter
{
    // Easy enough
    public async Task WaitAsync(TimeSpan delay, CancellationToken token = default)
    {
        try
        {
            await Task.Delay(delay, token)
        }
        catch(OperationCancelledException)
        {
        }
    }

    // Not so straightforward
    public void Wait(TimeSpan delay, CancellationToken token = default)
    {
        WaitAsync(delay, token).GetAwaiter().GetResult(); // May deadlock
        WaitAsync(delay, token).Wait();                   // May deadlock, also doesn't propagate exceptions properly
        WaitAsync(delay).Wait(token);                     // Even worse
        Thread.Sleep(delay)                               // Doesn't support cancellation
        token.WaitHandle.WaitOne(delay, true);            // Maybe? Not sure how the second parameter works when I have no control over the context
        token.WaitHandle.WaitOne(delay, false);           // No idea
    }
}

【问题讨论】:

  • 我认为如果你配置任务不在同一上下文中恢复,那么同步等待是安全的。例如,`var task = AwaitAsync(delay, token); task.ConfigureAwait(false); task.Wait();` 但是,如果代码执行未在捕获的上下文中恢复,您可能会遇到线程同步问题。尤其是 GUI 应用程序不允许其他非 GUI 线程修改视图
  • “一个担心是在异步方法上同步等待会导致死锁” -- 是的,这是一个担心。但是,这并不意味着应该担心它。你的 API 要么自然是异步的,要么是同步的;以自然的方式暴露它。让客户担心如何以另一种方式调用它。例如,客户端可以使用 Task.Run() 包装您的自然同步 API。或者您的自然异步 API 可以通过调用 Wait()Result 被客户端阻止,让他们担心并解决这样做所涉及的危险。
  • @MarcGravell 等待异步方法不是必需的,我只想等待特定的时间。方法Wait 不必调用WaitAsync
  • @zafar:OP 可以澄清,但我对该选项的理解是他们依赖于delay,它会在一段时间后超时等待。 IE。它们使用delay 实现“等待”,并使用句柄表示取消。
  • “将同时具有相同功能的同步和异步版本”——这是一个根本性的错误。见标记重复。如果您想要回答有关等待的更具体的问题,您需要发布更具体的问题。从您的问题中甚至不清楚您是否字面上想要等待一段时间,如果是的话,为什么(这有什么意义?)。即使你这样做了,因为等待句柄实际上是实现带取消的阻塞线程等待的常用方法,你应该尝试过,然后解释为什么这对你不起作用。

标签: c# .net delay deadlock cancellation


【解决方案1】:

This answer几乎回答了你所有的问题。具体来说,它解释了通过一个调用另一个来实现同步和异步 API 并不是一个好主意。如果你遇到这种情况,我推荐boolean argument hack

对于您的具体问题,理想情况下您会使用同步等待 (Thread.Sleep),但由于它不支持 CancellationToken,所以这不是一个选项。所以你需要自己写。如您的问题所述,WaitHandle 有一个可以使用的超时参数:

public void Wait(TimeSpan delay, CancellationToken token = default)
{
  token.WaitHandle.WaitOne(delay);
}

值得看看Polly 是如何处理这个问题的,事实上,他们以这种方式实现了他们的synchronous cancelable delay

public static Action<TimeSpan, CancellationToken> Sleep = (timeSpan, cancellationToken) =>
{
  if (cancellationToken.WaitHandle.WaitOne(timeSpan))
    cancellationToken.ThrowIfCancellationRequested();
};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-21
    • 2012-11-05
    • 1970-01-01
    • 1970-01-01
    • 2012-11-23
    • 2016-02-24
    相关资源
    最近更新 更多