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

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

/// <summary>
/// 监听端口 创建WebSocket
/// </summary>
/// <param name="httpListener"></param>
private void CreateWebSocket(HttpListener httpListener)
{
if (!httpListener.IsListening)
throw new Exception("HttpListener未启动");
HttpListenerContext listenerContext = httpListener.GetContextAsync().Result; if (!listenerContext.Request.IsWebSocketRequest)
{
CreateWebSocket(httpListener);
return;
} WebSocketContext webSocket = null;
try
{
webSocket = new WebSocketContext(listenerContext, SubProtocol);
}
catch (Exception ex)
{
log.Error(ex);
CreateWebSocket(HttpListener);
return;
} log.Info($"成功创建WebSocket:{webSocket.ID}"); int workerThreads = , completionPortThreads = ;
ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);
if (workerThreads <= ReservedThreadsCount + || completionPortThreads <= ReservedThreadsCount + )
{
/**
* 可用线程小于预留线程数量
* 通知客户端关闭连接
* */
webSocket.CloseAsync(WebSocketCloseStatus.InternalServerError, "可用线程不足,无法连接").Wait();
}
else
{
if (OnReceiveMessage != null)
webSocket.OnReceiveMessage += OnReceiveMessage;
webSocket.OnCloseWebSocket += WebSocket_OnCloseWebSocket;
webSocketContexts.Add(webSocket); // 在线程中监听客户端发送的消息
ThreadPool.QueueUserWorkItem(new WaitCallback(p =>
{
(p as WebSocketContext).ReceiveMessageAsync().Wait();
}), webSocket); } CreateWebSocket(HttpListener);
}

在线程中添加监听代码

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

/// <summary>
/// 递归 同步接收消息
/// </summary>
/// <returns></returns>
public void ReceiveMessage()
{
WebSocket webSocket = HttpListenerWebSocketContext.WebSocket; if (webSocket.State != WebSocketState.Open)
throw new Exception("Http未握手成功,不能接受消息!"); var byteBuffer = WebSocket.CreateServerBuffer(ReceiveBufferSize);
WebSocketReceiveResult receiveResult = null;
try
{
receiveResult = webSocket.ReceiveAsync(byteBuffer, cancellationToken).Result;
}
catch (WebSocketException ex)
{
if (ex.InnerException is HttpListenerException)
{
log.Error(ex);
CloseAsync(WebSocketCloseStatus.ProtocolError, "客户端断开连接" + ex.Message).Wait(TimeSpan.FromSeconds());
return;
}
else
{
log.Error(ex);
CloseAsync(WebSocketCloseStatus.ProtocolError, "WebSocket 连接异常" + ex.Message).Wait(TimeSpan.FromSeconds());
return;
}
}
catch (Exception ex)
{
log.Error(ex);
CloseAsync(WebSocketCloseStatus.ProtocolError, "客户端断开连接" + ex.Message).Wait(TimeSpan.FromSeconds());
return;
}
if (receiveResult.CloseStatus.HasValue)
{
log.Info("接受到关闭消息!");
CloseAsync(receiveResult.CloseStatus.Value, receiveResult.CloseStatusDescription).Wait(TimeSpan.FromSeconds());
return;
} byte[] bytes = new byte[receiveResult.Count];
Array.Copy(byteBuffer.Array, bytes, bytes.Length); string message = Encoding.GetString(bytes);
log.Info($"{ID}接收到消息:{message}"); if (OnReceiveMessage != null)
OnReceiveMessage.Invoke(this, message); if (!cancellationToken.IsCancellationRequested)
ReceiveMessage();
}

接受消息方法

这是不能接受的。

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

该枚举有个字段LongRunning

LongRunning 2

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

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

        /// <summary>
/// 测试任务
/// 只运行了9个任务
/// </summary>
[TestMethod]
public void TestTask1()
{
var cts = new CancellationTokenSource();
int MaxWorkerThreads = , MaxCompletionPortThreads = ;
ThreadPool.GetMaxThreads(out MaxWorkerThreads, out MaxCompletionPortThreads);
Console.WriteLine($"最大可用辅助线程数目为{MaxCompletionPortThreads},最大可用异步 I/O 线程数目为{MaxCompletionPortThreads}");
MaxWorkerThreads = ;
MaxCompletionPortThreads = ;
Console.WriteLine(@"设置线程池中辅助线程的最大数目为{0}, 线程池中异步 I/O 线程的最大数目为{1}
同时运行30个长时运行线程,每个线程中运行一个同步方法,看是否30个线程是否都能运行。", MaxWorkerThreads, MaxCompletionPortThreads);
ThreadPool.SetMaxThreads(, );
ThreadPool.GetMaxThreads(out MaxWorkerThreads, out MaxCompletionPortThreads);
Console.WriteLine($"最大可用辅助线程数目为{MaxCompletionPortThreads},最大可用异步 I/O 线程数目为{MaxCompletionPortThreads}"); int count = ;
while (count++ < )
{
Task.Factory.StartNew(p =>
{
int index = (int)p;
int runCount = ;
LongRunningTask($"线程{index}", runCount, cts.Token);
}, count, cts.Token, TaskCreationOptions.None, TaskScheduler.Default);
} Task.Delay(TimeSpan.FromSeconds()).Wait(TimeSpan.FromSeconds()); // 等待超时,等待任务没有执行
cts.Cancel();
} /// <summary>
/// 长时运行任务
/// 递归运行
/// </summary>
/// <param name="taskName">任务名称</param>
/// <param name="runCount">运行次数</param>
/// <param name="token">传播有关取消操作的通知</param>
private void LongRunningTask(string taskName, int runCount, CancellationToken token)
{
PrintTask($"任务【{taskName}】线程ID【{Environment.CurrentManagedThreadId}】第【{++runCount}】次运行").Wait();
if (!token.IsCancellationRequested)
LongRunningTask(taskName, runCount, token);
}
/// <summary>
/// 异步打印任务 等待1秒后打印消息
/// </summary>
/// <param name="message">消息</param>
/// <returns></returns>
private Task PrintTask(string message)
{
return Task.Factory.StartNew(() =>
{
Thread.Sleep();
Console.WriteLine(message);
});
}

测试代码

测试结果

测试用了20秒才完成

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

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

设置了 TaskCreationOptions.LongRunning  代码如下:

        /// <summary>
/// 测试长时运行任务
/// 30个任务全部都运行了
/// </summary>
[TestMethod]
public void TestTaskLongRunning()
{
var cts = new CancellationTokenSource();
int MaxWorkerThreads = , MaxCompletionPortThreads = ;
ThreadPool.GetMaxThreads(out MaxWorkerThreads, out MaxCompletionPortThreads);
MaxWorkerThreads = ;
MaxCompletionPortThreads = ;
Console.WriteLine($"最大可用辅助线程数目为{MaxCompletionPortThreads},最大可用异步 I/O 线程数目为{MaxCompletionPortThreads}");
Console.WriteLine(@"设置线程池中辅助线程的最大数目为{0}, 线程池中异步 I/O 线程的最大数目为{1}
同时运行30个长时运行线程,每个线程中运行一个同步方法,看是否30个线程是否都能运行。", MaxWorkerThreads, MaxCompletionPortThreads);
ThreadPool.SetMaxThreads(, );
ThreadPool.GetMaxThreads(out MaxWorkerThreads, out MaxCompletionPortThreads);
Console.WriteLine($"最大可用辅助线程数目为{MaxCompletionPortThreads},最大可用异步 I/O 线程数目为{MaxCompletionPortThreads}"); int count = ;
while (count++ < )
{ Task.Factory.StartNew(p =>
{
int index = (int)p;
int runCount = ;
LongRunningTask($"线程{index}", runCount, cts.Token);
}, count, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
} Task.Delay(TimeSpan.FromSeconds()).Wait(TimeSpan.FromSeconds()); // 等待没有超时,等待任务有执行
cts.Cancel();
}

测试代码

测试结果:

测试用了10秒完成

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

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

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

例如:

        /// <summary>
/// 测试长时运行任务
/// 只运行了前9个任务
/// </summary>
[TestMethod]
public void TestTaskLongRunning2()
{
var cts = new CancellationTokenSource();
int MaxWorkerThreads = , MaxCompletionPortThreads = ;
ThreadPool.GetMaxThreads(out MaxWorkerThreads, out MaxCompletionPortThreads);
Console.WriteLine($"最大可用辅助线程数目为{MaxCompletionPortThreads},最大可用异步 I/O 线程数目为{MaxCompletionPortThreads}"); MaxWorkerThreads = ;
MaxCompletionPortThreads = ;
Console.WriteLine(@"设置线程池中辅助线程的最大数目为{0}, 线程池中异步 I/O 线程的最大数目为{1}
同时运行30个长时运行线程,每个线程中运行一个异步方法,看是否30个线程是否都能运行。", MaxWorkerThreads, MaxCompletionPortThreads);
ThreadPool.SetMaxThreads(, );
ThreadPool.GetMaxThreads(out MaxWorkerThreads, out MaxCompletionPortThreads);
Console.WriteLine($"最大可用辅助线程数目为{MaxCompletionPortThreads},最大可用异步 I/O 线程数目为{MaxCompletionPortThreads}"); int count = ;
while (count++ < )
{ Task.Factory.StartNew(async p =>
{
int index = (int)p;
int runCount = ;
await LongRunningTaskAsync($"线程{index}", runCount, cts.Token);
}, count, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
} Task.Delay(TimeSpan.FromSeconds()).Wait(TimeSpan.FromSeconds()); // 等待没有超时,等待任务有执行
cts.Cancel();
}
/// <summary>
/// 异步长时运行任务
/// </summary>
/// <param name="taskName">任务名称</param>
/// <param name="runCount">运行次数</param>
/// <param name="token">传播有关取消操作的通知</param>
/// <returns></returns>
private async Task LongRunningTaskAsync(string taskName, int runCount, CancellationToken token)
{
await PrintTask($"任务【{taskName}】线程ID【{Environment.CurrentManagedThreadId}】第【{++runCount}】次运行");
if (!token.IsCancellationRequested)
await LongRunningTaskAsync(taskName, runCount, token);
}

测试代码

测试结果

测试用了10秒完成

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

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

WebSocket修改后的监听方法:

        /// <summary>
/// 监听端口 创建WebSocket
/// </summary>
/// <param name="httpListener"></param>
private void CreateWebSocket(HttpListener httpListener)
{
if (!httpListener.IsListening)
throw new Exception("HttpListener未启动");
HttpListenerContext listenerContext = httpListener.GetContext(); if (!listenerContext.Request.IsWebSocketRequest)
{
CreateWebSocket(httpListener);
return;
} WebSocketContext webSocket = null;
try
{
webSocket = new WebSocketContext(listenerContext, SubProtocol);
}
catch (Exception ex)
{
log.Error(ex);
CreateWebSocket(HttpListener);
return;
} log.Info($"成功创建WebSocket:{webSocket.ID}"); int workerThreads = , completionPortThreads = ;
ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);
if (OnReceiveMessage != null)
webSocket.OnReceiveMessage += OnReceiveMessage;
webSocket.OnCloseWebSocket += WebSocket_OnCloseWebSocket; Task.Factory.StartNew(() =>
{
webSocket.ReceiveMessage();
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default); CreateWebSocket(HttpListener);
}

修改后的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. Servlet 2.x 规范

    Servlet 2.x 规范 sun 公司制订的一种基于 Java 技术的 WEB 服务器功能的组件规范.1997 年六月,Servlet 1.0 版本发行,最新版本 Servlet 4.0 处于研发 ...

  2. “matplotlib display text must have all code points < 128 or use Unicode strings”解决方法

    import sys reload(sys) sys.setdefaultencoding('utf-8') 插入以上代码,便可解决.

  3. oracle 中删除表 drop delete truncate

    oracle 中删除表 drop delete truncate   相同点,使用drop delete truncate 都会删除表中的内容 drop table 表名 delete from 表名 ...

  4. 用rpm命令安装定时器crontab

    crontab -l  command not found 准备以下安装包: ls -l总用量 1004-rw-r--r-- 1 root root  76296 10月  9 16:01 croni ...

  5. Win7 VS2013环境使用cuda_7.5.18

    首先得吐槽下VS2015出来快一年了CUDA居然还不支持,没办法重装系统刚从2013升到2015,还得再装回一个2013用,只为学习CUDA... 然后安装的时候,如果你选择自定义组件安装,注意不要改 ...

  6. 将批量指定的docker镜像打成文件

      #/bin/bash tag= img1=hub.chinacloud.com.cn/onex.dev/one-task-scheduler:$tag img2=hub.chinacloud.co ...

  7. c# richTextBox1添加内容并将滚动条滚动到当前焦点处

    1.   StringBuilder sb = new StringBuilder(); StringBuilder的改变比string快多了 2. sb.Append("\r\n" ...

  8. Linux下启动tomcat报错RROR org.apache.catalina.core.StandardContext- Error starting static Resources java.lang.IllegalArgumentException: Document base /home/duiba/apache-tomcat/webapps/../webapps/manager do

    部署项目的时候,重启tomcat,死活起不来,很郁闷,网上巴拉了半天,结合自己的情况,找到了原因: 错误日志信息: 2018-12-13 13:52:26,992 [main] INFO org.ap ...

  9. 重启随机游走算法(RWR:Random Walk with Restart)

    1 pagerank算法的基本原理 Pagerank算法是Google的网页排名算法,由拉里佩奇发明.其基本思想是民主表决.在互联网上,如果一个网页被很多其他网页所链接,说明它受到普遍的承认和信赖,那 ...

  10. Switch 选择结构

    switch 选择器 一.语法 switch(变量名){ case 情况1: //代码块 break: case 情况1: //代码块 break: default(默认): //代码块 break: ...