2012-06-20 12 views
5

Ich versuche, einen AsyncController so zu verkabeln, dass ein Benutzer, der auf eine Bestellung auf der Bestellseite klickt, eine Benachrichtigung erhält, dass sich die Bestellung geändert hat . Mein Ansatz, dies zu implementieren, besteht darin, auf der Bestellseite lange Polling-Ajax-Anfragen zu machen, aber wie man einen skalierbaren AsyncController dazu macht, damit umzugehen, ist für mich nicht offensichtlich.Wie lang polling in MVC richtig durchgeführt wird

Also das ist was ich bisher habe, die ID ist die ID der Bestellung, die als geändert oder abgefragt für Änderungen signalisiert wird.

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

Ich habe mir How to do long-polling AJAX requests in ASP.NET MVC? angesehen. Ich versuche, alle Details über diesen Code zu verstehen, aber soweit ich diesen Code verstehe, blockiert er jeden Worker-Thread im Thread-Pool, der, soweit ich weiß, schließlich zu Thread-Hunger führen würde.

Also, wie sollte ich das auf eine schöne, skalierbare Weise implementieren? Denken Sie daran, dass ich keine Komponenten von Drittanbietern mehr verwenden möchte. Ich möchte ein gutes Verständnis dafür bekommen, wie dieses Szenario richtig implementiert wird.

Antwort

3

Eigentlich konnte ich dies implementieren, ohne die Worker-Threads zu blockieren, die Sache, die mir fehlte, war 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")); 
    } 
}