SslStream.WriteAsync“当另一个写入操作挂起时,无法调用BeginWrite方法”

时间:2012-09-26 23:15:27

标签: c# sslstream

如何在异步写入客户端时防止此问题

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

3 个答案:

答案 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方法。