我最近遇到了一个问题,尝试使用SendAsync方法广播到开放的WebSocket,接收带有消息&#34的InvalidOperationException;发送操作已在进行中"
深入研究WebSocket类的源代码,内部跟踪繁忙状态:
internal ChannelState _sendState;
// Represents the state of a single channel; send and receive have their own states
internal enum ChannelState {
Ready, // this channel is available for transmitting new frames
Busy, // the channel is already busy transmitting frames
Closed // this channel has been closed
}
理想情况下,如果我要将更新广播到WebSocket连接,我想提前知道它忙碌并执行其他一些处理(例如对消息进行排队)
这个状态被标记为内部似乎很奇怪 - 它是一个公共财产我可以简单地检查
context.WebSocket.SendState == ChannelState.Ready
WebSocket上SendAsync的正确模式是什么,可以防止抛出此异常?
我不愿通过反思破解对该财产的访问。
编辑以澄清:
WebSocket.State属性不会帮助这种情况。该属性使用此枚举:
public enum WebSocketState
{
None,
Connecting,
Open,
CloseSent,
CloseReceived,
Closed,
Aborted
}
打开套接字连接后,此语句将评估为" true"无论是否忙于发送:
context.WebSocket.State == WebSocketState.Open
答案 0 :(得分:1)
我已经实施了一个似乎可以满足我需求的解决方案。
基本问题是当两个线程试图在同一个WebSocket上发送时。
我的解决方案有一些部分,并且取决于它在AspNetWebSocketContect下运行的事实,因此我可以使用“Items”字典来存储有关当前连接的属性。
这是我目前在开发环境中使用的代码 - 我会监视它的扩展程度:
/// <summary>
/// Send a message to a specific client.
/// </summary>
/// <param name="context"></param>
/// <param name="buffer"></param>
/// <returns></returns>
private static async Task SendMessage(AspNetWebSocketContext context, ArraySegment<byte> buffer)
{
// Return control to the calling method immediately.
await Task.Yield();
// Make sure we have data.
if (buffer.Count == 0)
return;
// The state of the connection is contained in the context Items dictionary.
bool sending;
lock (context)
{
// Are we already in the middle of a send?
sending = (bool)context.Items["sending"];
// If not, we are now.
if (!sending)
context.Items["sending"] = true;
}
if (!sending)
{
// Lock with a timeout, just in case.
if (!Monitor.TryEnter(context.WebSocket, 1000))
{
// If we couldn't obtain exclusive access to the socket in one second, something is wrong.
await context.WebSocket.CloseAsync(WebSocketCloseStatus.InternalServerError, string.Empty, CancellationToken.None);
return;
}
try
{
// Send the message synchronously.
var t = context.WebSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
t.Wait();
}
finally
{
Monitor.Exit(context.WebSocket);
}
// Note that we've finished sending.
lock (context)
{
context.Items["sending"] = false;
}
// Handle any queued messages.
await HandleQueue(context);
}
else
{
// Add the message to the queue.
lock (context)
{
var queue = context.Items["queue"] as List<ArraySegment<byte>>;
if (queue == null)
context.Items["queue"] = queue = new List<ArraySegment<byte>>();
queue.Add(buffer);
}
}
}
/// <summary>
/// If there was a message in the queue for this particular web socket connection, send it.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private static async Task HandleQueue(AspNetWebSocketContext context)
{
var buffer = new ArraySegment<byte>();
lock (context)
{
// Check for an item in the queue.
var queue = context.Items["queue"] as List<ArraySegment<byte>>;
if (queue != null && queue.Count > 0)
{
// Pull it off the top.
buffer = queue[0];
queue.RemoveAt(0);
}
}
// Send that message.
if (buffer.Count > 0)
await SendMessage(context, buffer);
}
我对这种方法的一些考虑因素: