【问题标题】:Thread.Sleep or Task.Delay in a synchronous method with both a sync and async caller具有同步和异步调用者的同步方法中的 Thread.Sleep 或 Task.Delay
【发布时间】:2017-02-13 12:03:45
【问题描述】:

我有一个同步方法:

public void DoStuff() {  
    DoThings(); 
    GraphClient.SetExtendedProperty(user, propertyName, value);     // this method occasionally throws an exception
    DoOtherThings();
}

Call3rdPartyMethod 使用 Azure Ad Graph Client API 进行 REST API 调用,如果我尝试在 Active Directory 上设置扩展属性的值但找不到它,则会引发异常。这通常发生在一个新用户被添加到目录中并且扩展属性功能在我想要设置值之前没有扩展用户架构(这似乎需要几秒钟)。

我用我自己的包装器替换了 SetExtendedProperty 调用,该包装器包含在忙等待循环中的调用,因此:

public void TrySetProperty(GraphObject user, string propertyName, string value)
{

   var exceptions = new List<Exception>();

   for (int retry = 0; retry < 5; retry++)
   {
      try
      { 
          if (retry > 0)
              Thread.Sleep(1000);
              GraphClient.SetExtendedProperty(user, propertyName, value);
      }
      catch (Exception ex)
      { 
          exceptions.Add(ex);
      }
   }

   throw new AggregateException(exceptions);
  }
}

问题是我希望能够从同步和异步方法调用TrySetProperty

public void DoStuff() {  
    DoThings(); 
    TrySetProperty(user, propertyName, value);
    DoOtherThings();
}

public Task DoOtherStuffAsync() {
    await DoAsyncThings();
    TrySetProperty(user, propertyName, value);
    await DoOtherAsyncThings();
}

我无法将 SetExtendedProperty 更改为异步的,而且我担心如果我从异步方法调用 Thread.Sleep - 而不是 Task.Delay(),我不应该使用它。谁能给点建议?

【问题讨论】:

  • 永远不要将阻塞代码与异步等待混合。这是等待发生的僵局。我建议以异步方式编写所有方法,不使用阻塞代码,然后使用this technique 为自己提供对异步方法的同步调用。
  • @Botonomous retry 是为了避免在第一次尝试时出现延迟,但在每次尝试之前插入延迟。
  • 删除了我的答案,因为@spender 是对的,这样混合异步和同步方法不是一个好建议。
  • 我不得不在生产中使用AsyncHelpers(在我之前的评论中指出的答案)一次,虽然它有效,但它并没有很好地放置。您真的需要同步吗?
  • 我可能刚刚找到了解决方案。 GetObjectById 上有一个 ExecuteSingleAsync().Result 示例:simple-talk.com/cloud/security-and-compliance/…。我想我现在可以编写两个版本的 TrySetProperty - 一个带有 Thread.Sleep 和一个带有 Task.Delay?

标签: c# multithreading asynchronous async-await thread-sleep


【解决方案1】:

我会推荐:

  • 如果可能,制作一个完全异步的版本。 REST API 自然是异步的,但一些客户端库仍然过时(即只有同步方法)。
  • 尽可能只公开异步版本(因为操作自然是异步的)。如果您必须支持同步 API(例如,为了向后兼容),请使用 boolean argument hack in my article on brownfield async
  • 使用Polly 进行重试逻辑。

假设您有一个完全异步版本的 GraphClient.SetExtendedProperty 工作,那么您的代码可能如下所示:

private static readonly Policy syncPolicy = Policy.Handle<Exception>().WaitAndRetry(5, _ => TimeSpan.FromSeconds(1));
private static readonly Policy asyncPolicy = Policy.Handle<Exception>().WaitAndRetryAsync(5, _ => TimeSpan.FromSeconds(1));

private static async Task TrySetProperty(GraphObject user, string propertyName, string value, bool sync)
{
    if (sync)
        syncPolicy.Execute(() => GraphClient.SetExtendedProperty(user, propertyName, value));
    else
        await asyncPolicy.ExecuteAsync(() => GraphClient.SetExtendedPropertyAsync(user, propertyName, value));
}

public static Task TrySetPropertyAsync(GraphObject user, string propertyName, string value) =>
    TrySetProperty(user, propertyName, value, sync: false);
public static void TrySetProperty(GraphObject user, string propertyName, string value) =>
    TrySetProperty(user, propertyName, value, sync: true).GetAwaiter().GetResult();

如果您的 TrySetProperty 逻辑真的那么简单(即,它实际上只在 GraphClient 上调用一个方法),那么您可以取消布尔参数 hack 以获得更简单的代码:

private static readonly Policy syncPolicy = Policy.Handle<Exception>().WaitAndRetry(5, _ => TimeSpan.FromSeconds(1));
private static readonly Policy asyncPolicy = Policy.Handle<Exception>().WaitAndRetryAsync(5, _ => TimeSpan.FromSeconds(1));

public static async Task TrySetPropertyAsync(GraphObject user, string propertyName, string value)
{
  await asyncPolicy.ExecuteAsync(() => GraphClient.SetExtendedPropertyAsync(user, propertyName, value));
}

public static void TrySetProperty(GraphObject user, string propertyName, string value)
{
  syncPolicy.Execute(() => GraphClient.SetExtendedProperty(user, propertyName, value));
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-04-07
    • 1970-01-01
    • 1970-01-01
    • 2020-12-19
    • 2017-09-23
    • 1970-01-01
    相关资源
    最近更新 更多