Search code examples
c#windows-store-appsasync-await

Socket.ConnectAsync for windows store application does not like Async


Windows store applications are frustrating to say the least; just close enough to regular .net to get into trouble.

My issue with working in Tasks, await, and Socket.ConnectAsync.

I've got the following code:

    public async Task<string> Connect(string hostName, int portNumber)
    {
        string result = string.Empty;

        // Create DnsEndPoint. The hostName and port are passed in to this method.
        DnsEndPoint hostEntry = new DnsEndPoint(hostName, portNumber);

        // Create a stream-based, TCP socket using the InterNetwork Address Family. 
        _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        // Create a SocketAsyncEventArgs object to be used in the connection request
        SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
        socketEventArg.RemoteEndPoint = hostEntry;

        // Inline event handler for the Completed event.
        // Note: This event handler was implemented inline in order to make this method self-contained.
        socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(delegate (object s, SocketAsyncEventArgs e)
        {
            // Retrieve the result of this request
            result = e.SocketError.ToString();

            // Signal that the request is complete, unblocking the UI thread
            _clientDone.Set();
        });

        // Sets the state of the event to nonsignaled, causing threads to block
        _clientDone.Reset();

        // Make an asynchronous Connect request over the socket
        await _socket.ConnectAsync(socketEventArg);

        // Block the UI thread for a maximum of TIMEOUT_MILLISECONDS milliseconds.
        // If no response comes back within this time then proceed
        _clientDone.WaitOne(TIMEOUT_MILLISECONDS);

        return result;
    }

And I started added in Async / await to the app to prevent UI issues. But when I went into this function and added the Await to

            await _socket.ConnectAsync(socketEventArg);

I get the error:

Error CS1929 'bool' does not contain a definition for 'GetAwaiter' and the best extension method overload 'WindowsRuntimeSystemExtensions.GetAwaiter(IAsyncAction)' requires a receiver of type 'IAsyncAction'

In looking at the docs for ConnectAsync it looks like ConnectAsync is supposed to support await...

Does it not support await?


Solution

  • No, ConnectAsync is not a TAP method, and thus cannot be used with await.

    My #1 recommendation for anyone using raw sockets is "don't". If you can, use a REST API (with HttpClient) or a SignalR API. Raw sockets have tons of pitfalls.

    If you must use raw sockets (i.e., the other side is using a custom TCP/IP protocol and you don't have the power to fix the situation), then the first thing to note is that the Socket class has three complete APIs all in one class.

    The first is the deceptively simple-looking synchronous API (Connect), which I do not recommend for any production code. The second is the standard APM pattern (BeginConnect/EndConnect). The third is a specialized asynchronous pattern that is specific to the Socket class (ConnectAsync); this specialized API is much more complex to use than the standard asynchronous API, and is only necessary when you have chatty socket communication in a constrained environment, and need to reduce the object churn through the garbage collector.

    Note that there is no await-compatible API. I haven't spoken to anyone at Microsoft about this, but my strong suspicion is that they simply thought the Socket class had too many members already (3 complete APIs; adding an await-compatible one would add a fourth complete API), and that's why it was skipped over when they added TAP-pattern (await-compatible) members to other types in the BCL.

    The correct API to use - easily 99.999% of the time - is the APM one. You can create your own TAP wrappers (which work with await) by using TaskFactory.FromAsync. I like to do this with extension methods, like this:

    public static Task ConnectTaskAsync(this Socket socket, EndPoint remoteEP)
    {
      return Task.Factory.FromAsync(socket.BeginConnect, socket.EndConnect, remoteEP, null);
    }
    

    You can then invoke it anywhere on a Socket, as such:

    await _socket.ConnectTaskAsync(hostEntry);