Dajbych.net


How to write a custom awaitable method

, 4 minutes to read

When you are call­ing an asyn­chronously waitable (awaitable) method, you may be cu­ri­ous how to write a cus­tom awaitable method your­self. It is very sim­ple espe­cially in case when the class con­tains only sin­gle awaitable method. Ev­ery awaitable method re­quires its own class other­wise.

The class Mes­sageWeb­Socket does not con­tain any asyn­chronous method for read­ing in­com­ing mes­sages. It is de­signed for event-driven pro­gram­ming. Us­ing this class is like read­ing a file con­tent by at­tach­ing to an event raised ev­ery time when the line is read. What we should do when we want just asyn­chronously wait for ev­ery mes­sage in the loop?

using System; using System.Threading.Tasks; using Windows.Networking.Sockets; using Windows.Security.Cryptography; public class WebSocket { private MessageWebSocket websocket = new MessageWebSocket(); private WebSocketReceiveAwaiter awaiter; public WebSocket() { awaiter = new WebSocketReceiveAwaiter(websocket); } public async Task Connect(string url) { await websocket.ConnectAsync(new Uri(url)); } public async Task SendMessage(string message) { var content = CryptographicBuffer.ConvertStringToBinary(message, BinaryStringEncoding.Utf8); await websocket.OutputStream.WriteAsync(content); } public WebSocketReceiveAwaiter ReadMessageAsync() { return awaiter; } public void Close() { if (websocket != null) { websocket.Dispose(); } } }

The Web­Socket class is a fa­cade. It al­lows to estab­lish a con­nec­tion, send mes­sages, re­ceive them and close the con­nec­tion. The method ReadMes­sage is awaitable and re­turns a struc­ture which is in­tel­li­gi­ble to the .NET Task-based async model.

using System; using System.Collections.Concurrent; using System.Runtime.CompilerServices; using System.Threading; using Windows.Networking.Sockets; public class WebSocketReceiveAwaiter : INotifyCompletion { private bool closed = false; private Action continuation = null; private SynchronizationContext syncContext = SynchronizationContext.Current; private ConcurrentQueue<string> messages = new ConcurrentQueue<string>(); internal WebSocketReceiveAwaiter(MessageWebSocket websocket) { websocket.Control.MessageType = SocketMessageType.Utf8; websocket.MessageReceived += MessageReceived; websocket.Closed += Closed; } private void MessageReceived(MessageWebSocket sender, MessageWebSocketMessageReceivedEventArgs args) { using (var reader = args.GetDataReader()) { if (args.MessageType == SocketMessageType.Utf8) { reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8; } var message = reader.ReadString(reader.UnconsumedBufferLength); if (message != null) { messages.Enqueue(message); Continue(); } } } private void Closed(IWebSocket sender, WebSocketClosedEventArgs args) { closed = true; Continue(); } private void Continue() { var continuation = Interlocked.Exchange(ref this.continuation, null); if (continuation != null) { syncContext.Post(state => { ((Action)state)(); }, continuation); } } public void OnCompleted(Action continuation) { Volatile.Write(ref this.continuation, continuation); } public string GetResult() { string message; if (messages.TryDequeue(out message)) { return message; } else { return null; } } public WebSocketReceiveAwaiter GetAwaiter() { return this; } public bool IsCompleted { get { return messages.Count > 0 || closed; } } }

The Web­Socke­tRe­ceiveAwaiter class re­ceives mes­sages and stores them to the queue. This is hap­pen­ing in non-UI thread. But the syn­chro­niza­tion con­text is cap­tured in class con­struc­tor as­sum­ing that the in­s­tance is cre­ated in the UI thread.

The method Con­tinue is called after ev­ery piece of work is fin­ished. Mean­while the Is­Com­pleted method re­turns true whether a piece of work is fin­ished. Fi­nally, the Ge­tRe­sult method re­turns the re­sult. Al­though the INo­ti­fy­Com­ple­tion in­ter­face de­fines only the On­Com­pleted method, com­piler ex­pects also pres­ence of Ge­tRe­sult and GetAwaiter meth­ods and Is­Com­pleted prop­erty.

var ws = new WebSocket(); await ws.Connect("ws://echo.websocket.org"); await ws.SendMessage("Test message 1."); await ws.SendMessage("Test message 2."); string msg; while ((msg = await ws.ReadMessageAsync()) != null) { await (new MessageDialog(msg)).ShowAsync(); }

This piece of code demon­s­trates how the Web­Socket class could be used. Real ap­proach heav­ily de­pends on what you want to achieve. The server used in this ex­am­ple sends back re­ceived mes­sages. The loop fin­ishes when server closes the con­nec­tion or when the Web­Socket.Close method is called.

Code ex­am­ples above were written for Win­dows Run­time, now called the Win­dows Uni­ver­sal Plat­form which is a pre­de­ces­sor of .NET Core and should be part of the .NET Stan­dard 1.1 in the fu­ture.