如何在异步写入客户端时防止此问题
The BeginWrite method cannot be called when another write operation is pending
mycode的
public async void Send(byte[] buffer)
{
if (buffer == null)
return;
await SslStream.WriteAsync(buffer, 0, buffer.Length);
}
答案 0 :(得分:10)
确切了解await关键字的作用非常重要:
await表达式不会阻塞它所在的线程 执行。相反,它会导致编译器注册其余部分 异步方法作为等待任务的延续。然后控制 返回异步方法的调用者。当任务完成时,它 调用它的继续,并重新执行异步方法 它停止的地方(MSDN - await (C# Reference))。
使用某些非空缓冲区调用Send时,您将进入
await SslStream.WriteAsync(buffer, 0, buffer.Length);
使用等待只在Send方法中阻止执行,但即使WriteAsync尚未完成,调用方中的代码也会继续执行。现在,如果在WriteAsync完成之前再次调用Send方法,您将获得已发布的异常,因为SslStream不允许多次写入操作,并且您发布的代码不会阻止这种情况发生。
如果要确保先前的BeginWrite已完成,则必须更改Send方法以返回任务
async Task Send(SslStream sslStream, byte[] buffer)
{
if (buffer == null)
return;
await sslStream.WriteAsync(buffer, 0, buffer.Length);
}
并等待它完成,方法是使用await来调用它:
await Send(sslStream, message);
如果您不尝试从多个线程写入数据,这应该有效。
此处还有一些代码可以防止多个线程的写操作重叠(如果与代码正确集成)。它使用中间队列和异步编程模型(APM),并且工作得非常快。 您需要调用EnqueueDataForWrite来发送数据。
ConcurrentQueue<byte[]> writePendingData = new ConcurrentQueue<byte[]>();
bool sendingData = false;
void EnqueueDataForWrite(SslStream sslStream, byte[] buffer)
{
if (buffer == null)
return;
writePendingData.Enqueue(buffer);
lock (writePendingData)
{
if (sendingData)
{
return;
}
else
{
sendingData = true;
}
}
Write(sslStream);
}
void Write(SslStream sslStream)
{
byte[] buffer = null;
try
{
if (writePendingData.Count > 0 && writePendingData.TryDequeue(out buffer))
{
sslStream.BeginWrite(buffer, 0, buffer.Length, WriteCallback, sslStream);
}
else
{
lock (writePendingData)
{
sendingData = false;
}
}
}
catch (Exception ex)
{
// handle exception then
lock (writePendingData)
{
sendingData = false;
}
}
}
void WriteCallback(IAsyncResult ar)
{
SslStream sslStream = (SslStream)ar.AsyncState;
try
{
sslStream.EndWrite(ar);
}
catch (Exception ex)
{
// handle exception
}
Write(sslStream);
}
答案 1 :(得分:2)
如果使用BeginWrite启动操作,请在开始下一个操作之前调用SslStream.EndWrite以结束旧的写操作。如果使用WriteAsync开始操作,请确保先完成任务,例如使用await
关键字或Wait()。
答案 2 :(得分:0)
其他答案很好,但我认为更简洁的解释(这是我搜索相同错误消息时所要查找的内容)是
该方法不是线程安全的,而是以非线程安全的方式调用的,因为您没有import { Logger, Inject, Controller } from '@nestjs/common';
import { SubscribeMessage,
WebSocketGateway,
MessageBody,
WebSocketServer,
OnGatewayInit,
OnGatewayConnection,
OnGatewayDisconnect} from '@nestjs/websockets';
import { Socket, Server } from 'socket.io';
import { ClientProxy, EventPattern} from '@nestjs/microservices';
@Controller('gateway')
@WebSocketGateway()
export class EventsGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect{
@WebSocketServer() server: Server;
private logger: Logger = new Logger('EventsGateway');
constructor(@Inject('EVENTS_SERVICE') private readonly client: ClientProxy) {
}
afterInit(server: Server) {
this.server = server;
this.logger.log('Init', EventsGateway.name);
}// OnGatewayInit
handleDisconnect(client: Socket) {
this.logger.log(`Client disconnected: ${client.id}`, EventsGateway.name);
}// OnGatewayDisconnect
handleConnection(client: Socket, ...args: any[]) {
this.logger.log(`Client connected: ${client.id}`, EventsGateway.name);
this.server.emit('client.events', 'Welcome client:' + client.id);
}// OnGatewayConnection
@SubscribeMessage('client.events')
handleEvent(@MessageBody() data: string): any {
return { event: 'client.events', 'data': data + '__from Server'};
}
@EventPattern('gw-event:playback.state')
async handlePlaybackState(data: Record<string, unknown>) {
this.server.emit('client.events', data); //<= Error emit is null
}
}
使用await
方法。