关于asp.net mvc:如何在MVC 3中正确进行长时间轮询

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