首页 > 解决方案 > 与取消同步等待的安全方法是什么?

问题描述

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

我试图想出一种方法来等待特定的时间量,这种方法在所有情况下都不会出现死锁或其他问题。一种担心是在异步方法上同步等待会导致死锁

考虑这个类,你应该如何实现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
    }
}

标签: c#.netdelaydeadlockcancellation

解决方案


这个答案 几乎回答了你所有的问题。具体来说,它解释了通过一个调用另一个来实现同步和异步 API 并不是一个好主意。如果您处于这种情况,我建议使用布尔参数 hack

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

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

值得看看Polly是如何处理这个问题的,事实上,他们以这种方式实现了同步可取消延迟:

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

推荐阅读