Jim
Jim

Reputation: 16022

race condition in async / await socket connection

This send method will fail periodically because control is returned to the calling thread, and Send is called again just as the socket is opened.

I am using Stephen Toub's class: http://blogs.msdn.com/b/pfxteam/archive/2011/12/15/10248293.aspx

This causes the exception: A connect request was made on an already connected socket.
What's the correct approach to ensure that the asynchronous method doesn't suffer this fate. Basically the send cannot actually complete before the connection is made.

    public async Task Send(byte[] tosend)
    {
        // this code is not correct / not thread safe.
        if (this.socket.Connected)
            await this.StartSending(tosend);
        else
            await StartConnecting(tosend);
    }

    private async Task StartSending(byte[] tosend)
    {
        var args = new SocketAsyncEventArgs();
        args.SetBuffer(tosend, 0, tosend.Length);
        var awaitable = new SocketAwaitable(args);
        await this.socket.SendAsync(awaitable);
    }

    private async Task StartConnecting(byte[] tosend)
    {
        var local = Dns.GetHostEntry(Dns.GetHostName());
        var ep = new IPEndPoint(local.AddressList.First(_ => _.AddressFamily == AddressFamily.InterNetwork), this.port);
        var args = new SocketAsyncEventArgs() { RemoteEndPoint = ep };
        var awaitable = new SocketAwaitable(args);
        await this.socket.ConnectAsync(awaitable);
        await StartSending(tosend);
    }

my test just calls Send very quickly in succession.

var sends = arrays.Select(_ => writer.Send(_)).ToArray();
Task.WaitAll(sends);

sometimes the code above works just fine, and the socket is connected, socket.Connected is set to true 'on time'. Other times the call to this.socket.ConnectAsync happens more than once.

Upvotes: 2

Views: 714

Answers (1)

David Pfeffer
David Pfeffer

Reputation: 39872

The calls to Send should be awaited, both so you know the connection has established and also so that you know that your data is being sent sequentially. This would better be handled by a foreach loop than .Select.

foreach (var data in arrays)
    await writer.Send(data);

Presumably you create the variable sends so that you can Task.WhenAll on it. Unfortunately this won't work for the reason you already discovered. You could possibly solve this with a separate method called Connect that async-ly connects to the remote host but does not send the data. You could then do:

await writer.Connect();
var sends = arrays.Select(_ => writer.Send(_)).ToArray();

Upvotes: 2

Related Questions