Dajbych.net


Jak napsat vlastní metodu Awaitable

, 3 minuty čtení

net2015 logo

Když voláte metodu asynchronously waitable (awaitable), můžete být zvědaví, jak si sami napsat vlastní metodu awaitable. Je to velmi jednoduché, zejména v případě, kdy třída obsahuje pouze jednu očekávatelnou metodu. Každá metoda awaitable vyžaduje svou vlastní třídu, jinak.

Třída MessageWebSocket neobsahuje žádnou asynchronní metodu pro čtení příchozích zpráv. Je určen pro programování řízené událostmi. Použití této třídy je jako čtení obsahu souboru připojením k události vyvolané při každém čtení řádku. Co máme dělat, když chceme asynchronně čekat na každou zprávu ve smyčce?

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

}

Třída WebSocket je fasáda. Umožňuje vám navázat spojení, odesílat zprávy, přijímat je a ukončit spojení. Metoda ReadMessage je očekávaná a vrací strukturu, která je srozumitelná pro asynchronní model založený na úlohách .NET.

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

}

Třída WebSocketReceiveAwaiter přijímá zprávy a ukládá je do fronty. K tomu dochází ve vlákně bez uživatelského rozhraní. Kontext synchronizace je však zachycen v konstruktoru třídy za předpokladu, že instance je vytvořena ve vlákně uživatelského rozhraní.

Metoda Continue se volá po dokončení každé práce. Mezitím metoda IsCompleted vrátí hodnotu true, když je část práce dokončena. Nakonec metoda GetResult vrátí výsledek. Přestože rozhraní INotifyCompletion definuje pouze metodu OnCompleted, kompilátor očekává přítomnost metod GetResult a GetAwaiter a vlastnosti IsCompleted.

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

Tato část kódu ukazuje, jak lze použít třídu WebSocket. Skutečný přístup do značné míry závisí na tom, čeho chcete dosáhnout. Server použitý v tomto příkladu odesílá přijaté zprávy zpět. Smyčka je ukončena, když server ukončí připojení nebo když je zavolána metoda WebSocket.Close.

Výše uvedené příklady kódu byly napsány pro prostředí Windows Runtime, nyní nazývané Univerzální platforma Windows, která je předchůdcem .NET Core a v budoucnu by měla být součástí .NET Standard 1.1.