2

在我的 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。

我到底要怎么写这样的东西?

4

2 回答 2

5

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

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;
  }
}

PS 如果你有兴趣,我的 AsyncEx 库有一整套async兼容的同步原语,灵感来自Stephen Toub 的博客系列

于 2013-01-07T02:38:25.763 回答
3

简单的解决方案:

使用并发队列。

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

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

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

于 2013-01-06T09:47:45.717 回答