【问题标题】:How to do long polling properly in MVC 3如何在 MVC 3 中正确进行长轮询
【发布时间】:2012-06-22 11:07:35
【问题描述】:

我正在尝试连接 AsyncController,以便当用户在订单页面上单击保存订单时,查看同一订单的所有用户都应该收到订单已更改的通知。我实现这一点的方法是在订单页面上执行长轮询 ajax 请求,但是如何制作可扩展的 AsyncController 来处理这对我来说并不明显。

这就是我目前所拥有的,ID 是指示已更改或轮询更改的订单的 ID。

public class MessageController : AsyncController
{
    static readonly ConcurrentDictionary<int, AutoResetEvent> Events = new ConcurrentDictionary<int, AutoResetEvent>();

    public ActionResult Signal(int id)
    {
        AutoResetEvent @event;
        if (Events.TryGetValue(id, out @event))
            @event.Set();

        return Content("Signal");
    }

    public void WaitAsync(int id)
    {
        Events.TryAdd(id, new AutoResetEvent(false));

        // TODO: This "works", but I should probably not block this thread.
        Events[id].WaitOne();
    }

    public ActionResult WaitCompleted()
    {
        return Content("WaitCompleted");
    }
}

我看过 How to do long-polling AJAX requests in ASP.NET MVC? 。我试图了解有关此代码的所有详细信息,但据我了解此代码它阻塞了线程池中的每个工作线程,据我所知,这最终会导致线程饥饿。

那么,我应该如何以一种很好的、​​可扩展的方式来实现它?请记住,我不想再使用任何第三方组件,我想很好地了解如何正确实施此方案。

【问题讨论】:

    标签: asp.net-mvc asynchronous long-polling


    【解决方案1】:

    实际上我能够在不阻塞工作线程的情况下实现这一点,我缺少的是 ThreadPool.RegisterWaitForSingleObject。

    public class ConcurrentLookup<TKey, TValue>
    {
        private readonly Dictionary<TKey, List<TValue>> _lookup = new Dictionary<TKey, List<TValue>>();
    
        public void Add(TKey key, TValue value)
        {
            lock (_lookup)
            {
                if (!_lookup.ContainsKey(key))
                    _lookup.Add(key, new List<TValue>());
    
                _lookup[key].Add(value);
            }
        }
    
        public List<TValue> Remove(TKey key)
        {
            lock (_lookup)
            {
                if (!_lookup.ContainsKey(key))
                    return new List<TValue>();
    
                var values = _lookup[key];
                _lookup.Remove(key);
    
                return values;
            }
        }
    }
    
    [SessionState(SessionStateBehavior.Disabled)]
    public class MessageController : AsyncController
    {
        static readonly ConcurrentLookup<int, ManualResetEvent> Events = new ConcurrentLookup<int, ManualResetEvent>();
    
        public ActionResult Signal(int id)
        {
            foreach (var @event in Events.Remove(id))
                @event.Set();
    
            return Content("Signal " + id);
        }
    
        public void WaitAsync(int id)
        {
            AsyncManager.OutstandingOperations.Increment();
    
            var @event = new ManualResetEvent(false);
    
            Events.Add(id, @event);
    
            RegisteredWaitHandle handle = null;
            handle = ThreadPool.RegisterWaitForSingleObject(@event, (state, timeout) => 
            {
                handle.Unregister(@event);
                @event.Dispose();
    
                AsyncManager.Parameters["id"] = id;
                AsyncManager.Parameters["timeout"] = timeout;
                AsyncManager.OutstandingOperations.Decrement();
            }, null, new TimeSpan(0, 2, 0), false);
        }
    
    
        public ActionResult WaitCompleted(int id, bool timeout)
        {
            return Content("WaitCompleted " + id + " " + (timeout? "Timeout" : "Signaled"));
        }
    }
    

    【讨论】:

      【解决方案2】:

      现在使用 async/await 实现长轮询要容易得多。

      public class MessageController : ApiController
      {
          private static readonly ConcurrentDictionary<int, ManualResetEventAsync> ManualResetEvents = new ConcurrentDictionary<int, ManualResetEventAsync>();
      
          [HttpGet]
          public IHttpActionResult Signal(int id)
          {
              if (ManualResetEvents.TryGetValue(id, out var manualResetEvent) == false)
              {
                  return Content(HttpStatusCode.OK, "Signal: No one waiting for signal");
              }
      
              manualResetEvent.Set();
      
              return Content(HttpStatusCode.OK, "Signaled: " + id);
          }
      
          [HttpGet]
          public async Task<IHttpActionResult> Wait(int id)
          {
              var manualResetEvent = ManualResetEvents.GetOrAdd(id, _ => new ManualResetEventAsync());
      
              var signaled = await manualResetEvent.WaitAsync(TimeSpan.FromSeconds(100));
      
              var disposed = manualResetEvent.DisposeIfNoWaiters();
              if (disposed)
              {
                  ManualResetEvents.TryRemove(id, out var _);
              }
      
              return Content(HttpStatusCode.OK, "Wait: " + (signaled ? "Signaled" : "Timeout") + " " + id);
          }
      }
      
      internal class ManualResetEventAsync
      {
          private readonly SemaphoreSlim semaphore = new SemaphoreSlim(0, int.MaxValue);
          private int waiters;
      
          public void Set()
          {
              semaphore.Release(int.MaxValue);
          }
      
          public async Task<bool> WaitAsync(TimeSpan timeSpan)
          {
              lock (semaphore)
              {
                  waiters++;
              }
      
              var task = await semaphore.WaitAsync(timeSpan);
      
              lock (semaphore)
              {
                  waiters--;
              }
      
              return task;
          }
      
          public bool DisposeIfNoWaiters()
          {
              lock (semaphore)
              {
                  if (waiters != 0)
                  {
                      return false;
                  }
      
                  semaphore.Dispose();
                  return true;
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2013-04-27
        • 2010-11-23
        • 1970-01-01
        • 2010-12-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-06-13
        • 1970-01-01
        相关资源
        最近更新 更多