【问题标题】:Proper way to sleep during a lock with await在等待锁定期间睡眠的正确方法
【发布时间】:2013-01-06 09:40:35
【问题描述】:

在我的 win8 (winrt, c#) 应用程序中,我需要调用一个 Web API,它有一个非常具体的限制:不再每 2 秒调用一次 Web 服务。

我已尝试强制执行此限制,如下所示:

class Client
{
    const int Delay = 2000;
    HttpClient m_client = new HttpClient();
    int m_ticks = 0;

    public async Task<string> Get(string url)
    {
        // Multiple threads could be calling, I need to protect access to m_ticks:
        string result = null;
        lock (this)
        {
            int ticks = Environment.TickCount - m_ticks;
            if (ticks < Delay)
                await Task.Delay(Delay - ticks);

            result = await m_client.GetStringAsync(url);

            m_ticks = Environment.TickCount;
        }

        return result;
    }
}

这让我陷入了困境:

  1. 我不能在锁中使用 await 语句。
  2. 我不能回退到 WebClient + Thread.Sleep 之类的东西(避免异步废话),因为它在 win8 客户端配置文件中不存在。
  3. 我无法避免这种方法是“异步”的,因为这样我就无法等待 GetStringAsync 或 Task.Delay 而不处于异步函数中。
  4. 我无法避免锁定,因为可能有多个线程调用此函数,并且我需要同步访问 m_ticks。

我该怎么写这样的东西?

【问题讨论】:

    标签: c# asynchronous windows-runtime httpclient async-await


    【解决方案1】:

    SemaphoreSlim 类型在 .NET 4.5 中进行了扩展,以包含 await 兼容的 WaitAsync 方法。它没有基于IDisposableRelease,但构建一个并不难:

    sealed class SemaphoreSlimReleaser : IDisposable
    {
      SemaphoreSlim mutex;
      public SemaphoreSlimReleaser(SemaphoreSlim mutex)
      {
        this.mutex = mutex;
      }
    
      void Dispose()
      {
        if (mutex == null)
          return;
        mutex.Release();
        mutex = null;
      }
    }
    

    然后您可以使用与您已有的非常相似的代码:

    class Client
    {
      const int Delay = 2000;
      HttpClient m_client = new HttpClient();
      int m_ticks = 0;
      SemaphoreSlim mutex = new SemaphoreSlim(1);
    
      public async Task<string> Get(string url)
      {
        // Multiple threads could be calling, I need to protect access to m_ticks:
        string result = null;
        await mutex.WaitAsync();
        using (new SemaphoreSlimReleaser(mutex))
        {
            int ticks = Environment.TickCount - m_ticks;
            if (ticks < Delay)
                await Task.Delay(Delay - ticks);
    
            result = await m_client.GetStringAsync(url);
    
            m_ticks = Environment.TickCount;
        }
    
        return result;
      }
    }
    

    附:如果您有兴趣,我的 AsyncEx 库中有一个受 Stephen Toub's blog series 启发的 full suite of async-compatible synchronization primitives

    【讨论】:

    • Dispose 上将mutex 设置为null 是否有特定原因?如果您想将SemaphoreSlim 重用于其他并发操作怎么办?
    • @TopinFrassi:SemaphoreSlimReleaser 仅代表该信号量上的单个“获取/释放”,而不是信号量本身。我将成员变量设置为null,这样多次处理就没有效果了。
    【解决方案2】:

    简单的解决方案:

    使用并发队列。

    http://msdn.microsoft.com/en-us/library/dd267265.aspx

    所有使用网络服务的请求都将被添加到队列中。

    您将拥有一个线程,每两秒将一个对象排入队列,使用它并返回答案。

    【讨论】:

    • 不优雅,但我想是有效的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-03
    • 2012-10-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多