PCL 中的 TPL 和监视器

本文关键字:监视器 TPL 中的 PCL | 更新日期: 2023-09-27 18:32:07

所以我正在编写一个客户端API PCL(.NET 4.5,SL 5,Win8,WP8.1,WP SL 8)库,我决定一次只允许一个HTTP请求。目前,我使用TPL来执行它们:

Task.Factory.FromAsync<Stream>(httpReq.BeginGetRequestStream, httpReq.EndGetRequestStream, null).ContinueWith<Task<WebResponse>>((requestStreamTask) =>
{
    return Task<WebResponse>.Factory.FromAsync(httpReq.BeginGetResponse, httpReq.EndGetResponse, null);
}).Unwrap().ContinueWith<HttpWebResponse>((getResponseTask) =>
    {
        return (HttpWebResponse)getResponseTask.Result;
    });

所以我想添加锁定以防止同时发出多个请求。我知道我可以在开始之前简单地打电话给Monitor.Enter,并在最后一ContinueWith打电话给Monitor.Exit。但是基于将锁定迁移到 TPL,由于线程问题,我无法使用这样的Monitor。像该帖子建议的那样,使用不同的阻止对象我没有问题,但据我所知,在我的 PCL 中,我唯一可用的锁是监视器。

那我该怎么办?

编辑:在下面与Yuval Itzchakov交谈后,我意识到我只有Monitor同步类的原因是因为我的PCL中有Silverlight 5支持。如果没有其他方法,我会考虑放弃对 SL5 的支持,但我宁愿不这样做。

编辑2:在搞砸之后,我意识到我确实有ManualResetEvent课,我可以使用它吗?

PCL 中的 TPL 和监视器

由于您正在编写 PCL 并包含一些较旧的平台(特别是 SL5),因此您的选择有点有限。SL5 不支持 TPL 数据流,SemaphoreSlim也不支持 TPL 数据流。

但是,HttpClient是,async/await也是如此。这些使您的代码比 Task.Factory.FromAsync + Unwrap + ContinueWith 干净得多。

对于可移植async就绪的同步和/或生产者/消费者队列,我推荐我自己的 AsyncEx 库。在这种情况下,一个AsyncLock就足够了;它可以以类似于SemaphoreSlim的方式使用:

private readonly HttpClient _client = new HttpClient();
private readonly AsyncLock _mutex = new AsyncLock();
public async Task<string> GetStringAsync(string url)
{
    using (await _mutex.LockAsync())
    {
        return await _client.GetStringAsync(url);
    }
}

首先,在该答案中,我使用了文档中声明PCL支持SemaphoreSlim,因此您可以使用它而不是Monitor

其次,正如Jon Skeet指出的那样,您可以使用TPL Dataflow的ActionBlock(在PCL(中也支持async-await

var block = new ActionBlock<HttpWebRequest>(request => 
{
    var result = await request.GetResponseAsync();
    // handle result
}
HttpWebRequest newRequest = // ...
block.Post(newRequest);

该块一次处理一个所有请求,您可以使用 Post 添加新请求一次。

与其使用非常冗长的FromAsync模式,不如使用支持 PCL 的 HttpClientSemaphoreSlim(如 Arnon 和 Jon 所述):

private SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1);
private HttpClient _httpClient = new HttpClient();
public async Task SendRequestAsync(HttpWebRequest request)
{
      await _semaphoreSlim.WaitAsync();
      try
      {
         return await _httpClient.SendAsync(request);
      }
      finally
      {
         _semaphoreSlim.Release();
      }
}