How to do long polling properly in MVC 3
我正在尝试连接AsyncController,以便当用户单击订单页面上的订单上的"保存"时,所有查看同一订单的用户都将收到有关订单已更改的通知。我实现此方法的方法是在订单页面上长时间轮询ajax请求,但是对我来说,如何制作一个可伸缩的AsyncController来解决这个问题并不明显。
这就是我到目前为止所拥有的,该ID是表示已更改或已轮询更改的订单的ID。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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"); } } |
我看过如何在ASP.NET MVC中进行长轮询AJAX请求? 。我试图了解有关此代码的所有详细信息,但据我所知,它正在阻塞线程池中的每个工作线程,据我所知,这最终会导致线程饥饿。
那么,我应该如何以一种不错的,可扩展的方式来实现它呢?请记住,我不想再使用任何第三方组件,我希望对如何正确实现此方案有一个很好的了解。
实际上,我能够在不阻塞工作线程的情况下实现此功能,我所缺少的是ThreadPool.RegisterWaitForSingleObject。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | 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")); } } |
现在,使用async / await进行长时间轮询要容易得多。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | 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; } } } |