最近在学WebSocket,服务端需要监听多个WebSocket客户端发送的消息。

开始的解决方法是每个WebSocket客户端都添加一个线程进行监听,代码如下:

  1. /// <summary>
  2. /// 监听端口 创建WebSocket
  3. /// </summary>
  4. /// <param name="httpListener"></param>
  5. private void CreateWebSocket(HttpListener httpListener)
  6. {
  7. if (!httpListener.IsListening)
  8. throw new Exception("HttpListener未启动");
  9. HttpListenerContext listenerContext = httpListener.GetContextAsync().Result;
  10.  
  11. if (!listenerContext.Request.IsWebSocketRequest)
  12. {
  13. CreateWebSocket(httpListener);
  14. return;
  15. }
  16.  
  17. WebSocketContext webSocket = null;
  18. try
  19. {
  20. webSocket = new WebSocketContext(listenerContext, SubProtocol);
  21. }
  22. catch (Exception ex)
  23. {
  24. log.Error(ex);
  25. CreateWebSocket(HttpListener);
  26. return;
  27. }
  28.  
  29. log.Info($"成功创建WebSocket:{webSocket.ID}");
  30.  
  31. int workerThreads = , completionPortThreads = ;
  32. ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);
  33. if (workerThreads <= ReservedThreadsCount + || completionPortThreads <= ReservedThreadsCount + )
  34. {
  35. /**
  36. * 可用线程小于预留线程数量
  37. * 通知客户端关闭连接
  38. * */
  39. webSocket.CloseAsync(WebSocketCloseStatus.InternalServerError, "可用线程不足,无法连接").Wait();
  40. }
  41. else
  42. {
  43. if (OnReceiveMessage != null)
  44. webSocket.OnReceiveMessage += OnReceiveMessage;
  45. webSocket.OnCloseWebSocket += WebSocket_OnCloseWebSocket;
  46. webSocketContexts.Add(webSocket);
  47.  
  48. // 在线程中监听客户端发送的消息
  49. ThreadPool.QueueUserWorkItem(new WaitCallback(p =>
  50. {
  51. (p as WebSocketContext).ReceiveMessageAsync().Wait();
  52. }), webSocket);
  53.  
  54. }
  55.  
  56. CreateWebSocket(HttpListener);
  57. }

在线程中添加监听代码

但是可用线程数量是有限的,先连接的客户端一直递归接收消息,导致线程无限占用,后连接上的客户端就没有线程用于监听接受消息了。
接受消息方法如下:

  1. /// <summary>
  2. /// 递归 同步接收消息
  3. /// </summary>
  4. /// <returns></returns>
  5. public void ReceiveMessage()
  6. {
  7. WebSocket webSocket = HttpListenerWebSocketContext.WebSocket;
  8.  
  9. if (webSocket.State != WebSocketState.Open)
  10. throw new Exception("Http未握手成功,不能接受消息!");
  11.  
  12. var byteBuffer = WebSocket.CreateServerBuffer(ReceiveBufferSize);
  13. WebSocketReceiveResult receiveResult = null;
  14. try
  15. {
  16. receiveResult = webSocket.ReceiveAsync(byteBuffer, cancellationToken).Result;
  17. }
  18. catch (WebSocketException ex)
  19. {
  20. if (ex.InnerException is HttpListenerException)
  21. {
  22. log.Error(ex);
  23. CloseAsync(WebSocketCloseStatus.ProtocolError, "客户端断开连接" + ex.Message).Wait(TimeSpan.FromSeconds());
  24. return;
  25. }
  26. else
  27. {
  28. log.Error(ex);
  29. CloseAsync(WebSocketCloseStatus.ProtocolError, "WebSocket 连接异常" + ex.Message).Wait(TimeSpan.FromSeconds());
  30. return;
  31. }
  32. }
  33. catch (Exception ex)
  34. {
  35. log.Error(ex);
  36. CloseAsync(WebSocketCloseStatus.ProtocolError, "客户端断开连接" + ex.Message).Wait(TimeSpan.FromSeconds());
  37. return;
  38. }
  39. if (receiveResult.CloseStatus.HasValue)
  40. {
  41. log.Info("接受到关闭消息!");
  42. CloseAsync(receiveResult.CloseStatus.Value, receiveResult.CloseStatusDescription).Wait(TimeSpan.FromSeconds());
  43. return;
  44. }
  45.  
  46. byte[] bytes = new byte[receiveResult.Count];
  47. Array.Copy(byteBuffer.Array, bytes, bytes.Length);
  48.  
  49. string message = Encoding.GetString(bytes);
  50. log.Info($"{ID}接收到消息:{message}");
  51.  
  52. if (OnReceiveMessage != null)
  53. OnReceiveMessage.Invoke(this, message);
  54.  
  55. if (!cancellationToken.IsCancellationRequested)
  56. ReceiveMessage();
  57. }

接受消息方法

这是不能接受的。

后来在Task中看到,在创建Task时可以设置TaskCreationOptions参数

该枚举有个字段LongRunning

LongRunning 2

指定任务将是长时间运行的、粗粒度的操作,涉及比细化的系统更少、更大的组件。 它会向 TaskScheduler 提示,过度订阅可能是合理的。 可以通过过度订阅创建比可用硬件线程数更多的线程。 它还将提示任务计划程序:该任务需要附加线程,以使任务不阻塞本地线程池队列中其他线程或工作项的向前推动。

经过测试,可同时运行的任务数量的确可以超出可用线程数量。
测试如下:
没有设置 TaskCreationOptions.LongRunning  代码如下:

  1. /// <summary>
  2. /// 测试任务
  3. /// 只运行了9个任务
  4. /// </summary>
  5. [TestMethod]
  6. public void TestTask1()
  7. {
  8. var cts = new CancellationTokenSource();
  9. int MaxWorkerThreads = , MaxCompletionPortThreads = ;
  10. ThreadPool.GetMaxThreads(out MaxWorkerThreads, out MaxCompletionPortThreads);
  11. Console.WriteLine($"最大可用辅助线程数目为{MaxCompletionPortThreads},最大可用异步 I/O 线程数目为{MaxCompletionPortThreads}");
  12. MaxWorkerThreads = ;
  13. MaxCompletionPortThreads = ;
  14. Console.WriteLine(@"设置线程池中辅助线程的最大数目为{0}, 线程池中异步 I/O 线程的最大数目为{1}
  15. 同时运行30个长时运行线程,每个线程中运行一个同步方法,看是否30个线程是否都能运行。", MaxWorkerThreads, MaxCompletionPortThreads);
  16. ThreadPool.SetMaxThreads(, );
  17. ThreadPool.GetMaxThreads(out MaxWorkerThreads, out MaxCompletionPortThreads);
  18. Console.WriteLine($"最大可用辅助线程数目为{MaxCompletionPortThreads},最大可用异步 I/O 线程数目为{MaxCompletionPortThreads}");
  19.  
  20. int count = ;
  21. while (count++ < )
  22. {
  23. Task.Factory.StartNew(p =>
  24. {
  25. int index = (int)p;
  26. int runCount = ;
  27. LongRunningTask($"线程{index}", runCount, cts.Token);
  28. }, count, cts.Token, TaskCreationOptions.None, TaskScheduler.Default);
  29. }
  30.  
  31. Task.Delay(TimeSpan.FromSeconds()).Wait(TimeSpan.FromSeconds()); // 等待超时,等待任务没有执行
  32. cts.Cancel();
  33. }
  34.  
  35. /// <summary>
  36. /// 长时运行任务
  37. /// 递归运行
  38. /// </summary>
  39. /// <param name="taskName">任务名称</param>
  40. /// <param name="runCount">运行次数</param>
  41. /// <param name="token">传播有关取消操作的通知</param>
  42. private void LongRunningTask(string taskName, int runCount, CancellationToken token)
  43. {
  44. PrintTask($"任务【{taskName}】线程ID【{Environment.CurrentManagedThreadId}】第【{++runCount}】次运行").Wait();
  45. if (!token.IsCancellationRequested)
  46. LongRunningTask(taskName, runCount, token);
  47. }
  48. /// <summary>
  49. /// 异步打印任务 等待1秒后打印消息
  50. /// </summary>
  51. /// <param name="message">消息</param>
  52. /// <returns></returns>
  53. private Task PrintTask(string message)
  54. {
  55. return Task.Factory.StartNew(() =>
  56. {
  57. Thread.Sleep();
  58. Console.WriteLine(message);
  59. });
  60. }

测试代码

测试结果

测试用了20秒才完成

主线程创建了一个等待10秒后完成的任务,任务等待超时20秒

说明主程序创建的任务没有执行,而是等待超时了。

设置了 TaskCreationOptions.LongRunning  代码如下:

  1. /// <summary>
  2. /// 测试长时运行任务
  3. /// 30个任务全部都运行了
  4. /// </summary>
  5. [TestMethod]
  6. public void TestTaskLongRunning()
  7. {
  8. var cts = new CancellationTokenSource();
  9. int MaxWorkerThreads = , MaxCompletionPortThreads = ;
  10. ThreadPool.GetMaxThreads(out MaxWorkerThreads, out MaxCompletionPortThreads);
  11. MaxWorkerThreads = ;
  12. MaxCompletionPortThreads = ;
  13. Console.WriteLine($"最大可用辅助线程数目为{MaxCompletionPortThreads},最大可用异步 I/O 线程数目为{MaxCompletionPortThreads}");
  14. Console.WriteLine(@"设置线程池中辅助线程的最大数目为{0}, 线程池中异步 I/O 线程的最大数目为{1}
  15. 同时运行30个长时运行线程,每个线程中运行一个同步方法,看是否30个线程是否都能运行。", MaxWorkerThreads, MaxCompletionPortThreads);
  16. ThreadPool.SetMaxThreads(, );
  17. ThreadPool.GetMaxThreads(out MaxWorkerThreads, out MaxCompletionPortThreads);
  18. Console.WriteLine($"最大可用辅助线程数目为{MaxCompletionPortThreads},最大可用异步 I/O 线程数目为{MaxCompletionPortThreads}");
  19.  
  20. int count = ;
  21. while (count++ < )
  22. {
  23.  
  24. Task.Factory.StartNew(p =>
  25. {
  26. int index = (int)p;
  27. int runCount = ;
  28. LongRunningTask($"线程{index}", runCount, cts.Token);
  29. }, count, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
  30. }
  31.  
  32. Task.Delay(TimeSpan.FromSeconds()).Wait(TimeSpan.FromSeconds()); // 等待没有超时,等待任务有执行
  33. cts.Cancel();
  34. }

测试代码

测试结果:

测试用了10秒完成

主线程创建了一个等待10秒后完成的任务,任务等待超时20秒

说明主程序创建的任务立即执行了,程序等待了10秒完成。

使用TaskCreationOptions.LongRunning  需要注意的是Action必须是同步方法同时运行任务书才能超出可以用线程数量,否则不能。

例如:

  1. /// <summary>
  2. /// 测试长时运行任务
  3. /// 只运行了前9个任务
  4. /// </summary>
  5. [TestMethod]
  6. public void TestTaskLongRunning2()
  7. {
  8. var cts = new CancellationTokenSource();
  9. int MaxWorkerThreads = , MaxCompletionPortThreads = ;
  10. ThreadPool.GetMaxThreads(out MaxWorkerThreads, out MaxCompletionPortThreads);
  11. Console.WriteLine($"最大可用辅助线程数目为{MaxCompletionPortThreads},最大可用异步 I/O 线程数目为{MaxCompletionPortThreads}");
  12.  
  13. MaxWorkerThreads = ;
  14. MaxCompletionPortThreads = ;
  15. Console.WriteLine(@"设置线程池中辅助线程的最大数目为{0}, 线程池中异步 I/O 线程的最大数目为{1}
  16. 同时运行30个长时运行线程,每个线程中运行一个异步方法,看是否30个线程是否都能运行。", MaxWorkerThreads, MaxCompletionPortThreads);
  17. ThreadPool.SetMaxThreads(, );
  18. ThreadPool.GetMaxThreads(out MaxWorkerThreads, out MaxCompletionPortThreads);
  19. Console.WriteLine($"最大可用辅助线程数目为{MaxCompletionPortThreads},最大可用异步 I/O 线程数目为{MaxCompletionPortThreads}");
  20.  
  21. int count = ;
  22. while (count++ < )
  23. {
  24.  
  25. Task.Factory.StartNew(async p =>
  26. {
  27. int index = (int)p;
  28. int runCount = ;
  29. await LongRunningTaskAsync($"线程{index}", runCount, cts.Token);
  30. }, count, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
  31. }
  32.  
  33. Task.Delay(TimeSpan.FromSeconds()).Wait(TimeSpan.FromSeconds()); // 等待没有超时,等待任务有执行
  34. cts.Cancel();
  35. }
  36. /// <summary>
  37. /// 异步长时运行任务
  38. /// </summary>
  39. /// <param name="taskName">任务名称</param>
  40. /// <param name="runCount">运行次数</param>
  41. /// <param name="token">传播有关取消操作的通知</param>
  42. /// <returns></returns>
  43. private async Task LongRunningTaskAsync(string taskName, int runCount, CancellationToken token)
  44. {
  45. await PrintTask($"任务【{taskName}】线程ID【{Environment.CurrentManagedThreadId}】第【{++runCount}】次运行");
  46. if (!token.IsCancellationRequested)
  47. await LongRunningTaskAsync(taskName, runCount, token);
  48. }

测试代码

测试结果

测试用了10秒完成

主线程创建了一个等待10秒后完成的任务,任务等待超时20秒

说明主程序创建的任务立即执行了,程序等待了10秒完成。

WebSocket修改后的监听方法:

  1. /// <summary>
  2. /// 监听端口 创建WebSocket
  3. /// </summary>
  4. /// <param name="httpListener"></param>
  5. private void CreateWebSocket(HttpListener httpListener)
  6. {
  7. if (!httpListener.IsListening)
  8. throw new Exception("HttpListener未启动");
  9. HttpListenerContext listenerContext = httpListener.GetContext();
  10.  
  11. if (!listenerContext.Request.IsWebSocketRequest)
  12. {
  13. CreateWebSocket(httpListener);
  14. return;
  15. }
  16.  
  17. WebSocketContext webSocket = null;
  18. try
  19. {
  20. webSocket = new WebSocketContext(listenerContext, SubProtocol);
  21. }
  22. catch (Exception ex)
  23. {
  24. log.Error(ex);
  25. CreateWebSocket(HttpListener);
  26. return;
  27. }
  28.  
  29. log.Info($"成功创建WebSocket:{webSocket.ID}");
  30.  
  31. int workerThreads = , completionPortThreads = ;
  32. ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);
  33. if (OnReceiveMessage != null)
  34. webSocket.OnReceiveMessage += OnReceiveMessage;
  35. webSocket.OnCloseWebSocket += WebSocket_OnCloseWebSocket;
  36.  
  37. Task.Factory.StartNew(() =>
  38. {
  39. webSocket.ReceiveMessage();
  40. }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default);
  41.  
  42. CreateWebSocket(HttpListener);
  43. }

修改后的WebSocket服务可以监听超过可用线程数量的客户端

TaskCreationOptions.LongRunning 运行比可用线程数更多的任务的更多相关文章

  1. .NET并行计算和并发6-获取线程池的最大可用线程数

    using System; using System.IO; using System.Security.Permissions; using System.Threading; class Test ...

  2. Jmeter任在运行,线程数上不去

    问题 jmeter在运行,但是线程数上不去(本来模型设计了100个总线程,但运行时线程只能上到5,根据图上观察总共也只能运行5个线程) 之前更新了random csv插件 解决办法 查看jmeter. ...

  3. JMeter命令行方式运行时动态设置线程数及其他属性(动态传参)

    在使用JMeter进行性能测试时,以下情况经常出现: 1.测试过程中,指定运行的线程数.指定运行循环次数不断改变: 2.访问的目标地址发生改变,端口发生改变,需要改写脚本. 上面的问题在GUI中,直接 ...

  4. Jvm支持的最大线程数

    摘自 http://blog.csdn.net/xyls12345/article/details/26482387 JVM最大线程数 (2012-07-04 23:20:15) 转载▼ 标签: jv ...

  5. web应用性能测试-Tomcat 7 连接数和线程数配置

    转自:http://www.jianshu.com/p/8445645b3aff 引言 这段时间折腾了哈java web应用的压力测试,部署容器是tomcat 7.期间学到了蛮多散碎的知识点,及时梳理 ...

  6. JVM可支持的最大线程数(转)

    摘自:http://sesame.iteye.com/blog/622670 工作中碰到过这个问题好几次了,觉得有必要总结一下,所以有了这篇文章,这篇文章分为三个部分:认识问题.分析问题.解决问题. ...

  7. 通过设置线程池的最小线程数来提高task的效率,SetMinThreads。

    http://www.cnblogs.com/Charltsing/p/taskpoolthread.html task默认对线程的调度是逐步增加的,连续多次运行并发线程,会提高占用的线程数,而等若干 ...

  8. JVM可支持的最大线程数

    转微博,因为他也是转载  不知道原出处 一.认识问题: 首先我们通过下面这个 测试程序 来认识这个问题:运行的环境 (有必要说明一下,不同环境会有不同的结果):32位 Windows XP,Sun J ...

  9. Tomcat参数调优包括日志、线程数、内存【转】

    [Tomcat中日志打印对性能测试的影响] 一般都提供了这样5个日志级别: ▪ Debug ▪ Info ▪ Warn ▪ Error ▪ Fatal 由于性能测试需要并发进行压力测试,如果日志级别是 ...

随机推荐

  1. Netty 源码(二)NioEventLoop 之 Channel 注册

    Netty 源码(二)NioEventLoop 之 Channel 注册 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 一 ...

  2. Null value was assigned to a property of primitive type setter of cn.itcast.oa.domain.Forum.topicCount

    [引用http://m.blog.csdn.net/blog/u013998070/41087351] Null value was assigned to a property of primiti ...

  3. Squares of a Sorted Array LT977

    Given an array of integers A sorted in non-decreasing order, return an array of the squares of each ...

  4. centos安装tomcat7.0.70

    抄自:https://www.cnblogs.com/www1707/p/6592504.html apache-tomcat-7.0.70jdk-7u67-linux-x64 下载tomcathtt ...

  5. python3版本main.py执行产生中间__pycache__详解

    __pycache__ 用python编写好一个工程,在第一次运行后,总会发现工程根目录下生成了一个__pycache__文件夹,里面是和py文件同名的各种 *.pyc 或者 *.pyo 文件. 先大 ...

  6. 初步认识linux的top命令

    今天学习了一下top命令,强大无比啊! top命令涉及到的东西很多.用来监视系统的运行状态,top打印包括cpu.内存.进程使用情况的统计信息,还打印出进程列表. 输入top命令,不带任何参数,默认打 ...

  7. python之函数篇3

    一:函数的定义 1)函数的简单使用,无参函数 def f1(): # 定义函数指定函数名 print("hello") # 指定功能 f1() # 调用函数,才能执行函数体里面的功 ...

  8. Spring框架简介

    1.发明者:Rod Johnson 2.轮子理论推崇者: 2.1 轮子理论:不用重复发明轮子 2.2 IT行业:直接只用写好的代码 3.Spring框架宗旨:不重新发明技术,让原有技术使用起来更加方便 ...

  9. 746. Min Cost Climbing Stairs

    两种方法,核心思想都一样,求出走到每一步上的最小开销,直到最后一步和倒数第二步,比较其最小值返回即可. 方法一,用一个辅助的容器 class Solution { public: int minCos ...

  10. 2018.12.05 codeforces 948C. Producing Snow(堆)

    传送门 维护一个堆. 每次先算出一个都不弹掉的总贡献. 然后把要弹掉的弹掉,并减去它们对应的贡献. 代码: #include<bits/stdc++.h> #define ri regis ...