Netwerkcommunicatie op de achtergrond

Als u netwerkcommunicatie wilt voortzetten terwijl deze zich niet op de voorgrond bevindt, kan uw app achtergrondtaken en een van deze twee opties gebruiken.

  • Socketbroker. Als uw app sockets gebruikt voor langdurige verbindingen, kan deze, wanneer deze de voorgrond verlaat, het eigendom van een socket delegeren aan een systeemsocketbroker. De broker activeert vervolgens uw app wanneer verkeer op de socket aankomt; draagt het eigendom weer over naar uw app; en uw app verwerkt vervolgens het binnenkomende verkeer.
  • Triggers van het besturingskanaal.

Netwerkbewerkingen uitvoeren in achtergrondtaken

  • Gebruik een SocketActivityTrigger om de achtergrondtaak te activeren wanneer een pakket wordt ontvangen en u moet een kortstondige taak uitvoeren. Nadat de taak is uitgevoerd, moet de achtergrondtaak worden beëindigd om energie te besparen.
  • Gebruik een ControlChannelTrigger om de achtergrondtaak te activeren wanneer een pakket wordt ontvangen en u moet een langdurige taak uitvoeren.

Netwerkgerelateerde voorwaarden en vlaggen

  • Voeg de voorwaarde InternetAvailable toe aan de achtergrondtaak BackgroundTaskBuilder.AddCondition om de achtergrondtaak uit te stellen totdat de netwerkstack wordt uitgevoerd. Deze voorwaarde bespaart energie omdat de achtergrondtaak pas wordt uitgevoerd als het netwerk actief is. Deze voorwaarde biedt geen realtime activering.

Ongeacht de trigger die u gebruikt, stelt u IsNetworkRequested in op uw achtergrondtaak om ervoor te zorgen dat het netwerk actief blijft terwijl de achtergrondtaak wordt uitgevoerd. Dit vertelt de infrastructuur van de achtergrondtaak om het netwerk actief te houden terwijl de taak wordt uitgevoerd, zelfs als het apparaat de Connected Standby-modus heeft opgegeven. Als uw achtergrondtaak IsNetworkRequested niet gebruikt, heeft uw achtergrondtaak geen toegang tot het netwerk in de modus Verbonden stand-by (bijvoorbeeld wanneer het scherm van een telefoon is uitgeschakeld).

Socketbroker en socketActivityTrigger

Als uw app gebruikmaakt van DatagramSocket-, StreamSocket- of StreamSocketListener-verbindingen , moet u SocketActivityTrigger en de socketbroker gebruiken om op de hoogte te worden gesteld wanneer verkeer voor uw app binnenkomt terwijl deze zich niet op de voorgrond bevinden.

Om ervoor te zorgen dat uw app gegevens ontvangt en verwerkt die zijn ontvangen op een socket wanneer uw app niet actief is, moet uw app een eenmalige installatie uitvoeren bij het opstarten en vervolgens het eigendom van socket overdragen naar de socketbroker wanneer deze overgaat naar een status waarin deze niet actief is.

De eenmalige installatiestappen zijn het maken van een trigger, het registreren van een achtergrondtaak voor de trigger en het inschakelen van de socketbroker:

  • Maak een SocketActivityTrigger en registreer een achtergrondtaak voor de trigger met de parameter TaskEntryPoint die is ingesteld op uw code voor het verwerken van een ontvangen pakket.
            var socketTaskBuilder = new BackgroundTaskBuilder();
            socketTaskBuilder.Name = _backgroundTaskName;
            socketTaskBuilder.TaskEntryPoint = _backgroundTaskEntryPoint;
            var trigger = new SocketActivityTrigger();
            socketTaskBuilder.SetTrigger(trigger);
            _task = socketTaskBuilder.Register();
  • Roep EnableTransferOwnership aan op de socket voordat u de socket bindt.
           _tcpListener = new StreamSocketListener();

           // Note that EnableTransferOwnership() should be called before bind,
           // so that tcpip keeps required state for the socket to enable connected
           // standby action. Background task Id is taken as a parameter to tie wake pattern
           // to a specific background task.  
           _tcpListener.EnableTransferOwnership(_task.TaskId, SocketActivityConnectedStandbyAction.Wake);
           _tcpListener.ConnectionReceived += OnConnectionReceived;
           await _tcpListener.BindServiceNameAsync("my-service-name");

Zodra uw socket correct is ingesteld, roept u TransferOwnership aan op de socket wanneer uw app op het punt staat te worden onderbroken, zodat deze wordt overgedragen aan een socketbroker. De broker bewaakt de socket en activeert uw achtergrondtaak wanneer gegevens worden ontvangen. Het volgende voorbeeld bevat een hulpprogramma TransferOwnership-functie om de overdracht voor StreamSocketListener-sockets uit te voeren. (Houd er rekening mee dat de verschillende typen sockets elk hun eigen TransferOwnership-methode hebben, dus u moet de methode aanroepen die geschikt is voor de socket waarvan u het eigendom bent dat u overdraagt. Uw code bevat waarschijnlijk een overbelaste TransferOwnership-helper met één implementatie voor elk sockettype dat u gebruikt, zodat de OnSuspending-code gemakkelijk leesbaar blijft.)

Een app draagt het eigendom van een socket over aan een socket broker en geeft de ID voor de achtergrondtaak door met behulp van de juiste van de volgende methoden:


// declare int _transferOwnershipCount as a field.

private async void TransferOwnership(StreamSocketListener tcpListener)
{
    await tcpListener.CancelIOAsync();

    var dataWriter = new DataWriter();
    ++_transferOwnershipCount;
    dataWriter.WriteInt32(_transferOwnershipCount);
    var context = new SocketActivityContext(dataWriter.DetachBuffer());
    tcpListener.TransferOwnership(_socketId, context);
}

private void OnSuspending(object sender, SuspendingEventArgs e)
{
    var deferral = e.SuspendingOperation.GetDeferral();

    TransferOwnership(_tcpListener);
    deferral.Complete();
}

In de gebeurtenisafhandelaar van uw achtergrondtaak:

  • Haal eerst een uitstel van een achtergrondtaak op, zodat u de gebeurtenis kunt afhandelen met behulp van asynchrone methoden.
var deferral = taskInstance.GetDeferral();
  • Extraheer vervolgens de SocketActivityTriggerDetails uit de gebeurtenisargumenten en zoek de reden waarom de gebeurtenis is gegenereerd:
var details = taskInstance.TriggerDetails as SocketActivityTriggerDetails;
    var socketInformation = details.SocketInformation;
    switch (details.Reason)
  • Als de gebeurtenis is gegenereerd vanwege socketactiviteit, maakt u een DataReader op de socket, laadt u de lezer asynchroon en gebruikt u vervolgens de gegevens volgens het ontwerp van uw app. Houd er rekening mee dat u het eigendom van de socket moet teruggeven aan de socketbroker, om opnieuw meldingen te krijgen van verdere socketactiviteit.

In het volgende voorbeeld wordt de tekst die op de socket is ontvangen, weergegeven in een toast.

case SocketActivityTriggerReason.SocketActivity:
            var socket = socketInformation.StreamSocket;
            DataReader reader = new DataReader(socket.InputStream);
            reader.InputStreamOptions = InputStreamOptions.Partial;
            await reader.LoadAsync(250);
            var dataString = reader.ReadString(reader.UnconsumedBufferLength);
            ShowToast(dataString);
            socket.TransferOwnership(socketInformation.Id); /* Important! */
            break;
  • Als de gebeurtenis is gegenereerd omdat een actieve timer is verlopen, moet uw code enkele gegevens via de socket verzenden om de socket actief te houden en de keep alive-timer opnieuw te starten. Nogmaals, het is belangrijk om het eigendom van de socket terug te geven aan de socket broker om verdere eventmeldingen te kunnen ontvangen:
case SocketActivityTriggerReason.KeepAliveTimerExpired:
            socket = socketInformation.StreamSocket;
            DataWriter writer = new DataWriter(socket.OutputStream);
            writer.WriteBytes(Encoding.UTF8.GetBytes("Keep alive"));
            await writer.StoreAsync();
            writer.DetachStream();
            writer.Dispose();
            socket.TransferOwnership(socketInformation.Id); /* Important! */
            break;
  • Als de gebeurtenis is geactiveerd omdat de socket is gesloten, brengt u de socket opnieuw tot stand en zorgt u ervoor dat u, nadat u de nieuwe socket hebt gemaakt, het eigendom ervan overdraagt aan de socketbroker. In dit voorbeeld worden de hostnaam en poort opgeslagen in lokale instellingen, zodat ze kunnen worden gebruikt om een nieuwe socketverbinding tot stand te brengen:
case SocketActivityTriggerReason.SocketClosed:
            socket = new StreamSocket();
            socket.EnableTransferOwnership(taskInstance.Task.TaskId, SocketActivityConnectedStandbyAction.Wake);
            if (ApplicationData.Current.LocalSettings.Values["hostname"] == null)
            {
                break;
            }
            var hostname = (String)ApplicationData.Current.LocalSettings.Values["hostname"];
            var port = (String)ApplicationData.Current.LocalSettings.Values["port"];
            await socket.ConnectAsync(new HostName(hostname), port);
            socket.TransferOwnership(socketId);
            break;
  • Vergeet niet uw uitstel te voltooien wanneer u klaar bent met het verwerken van de gebeurtenismelding:
  deferral.Complete();

Zie het voorbeeld SocketActivityStreamSocket voor een volledig voorbeeld waarin het gebruik van de SocketActivityTrigger en socketbroker wordt gedemonstreerd. De initialisatie van de socket wordt uitgevoerd in Scenario1_Connect.xaml.cs en de implementatie van de achtergrondtaak bevindt zich in SocketActivityTask.cs.

U zult waarschijnlijk merken dat in het voorbeeld TransferOwnership wordt aangeroepen zodra er een nieuwe socket wordt gemaakt of een bestaande socket wordt verkregen, in plaats van dat u de OnSuspending zelfs handler gebruikt om dit te doen, zoals beschreven in dit onderwerp. Dit komt doordat het voorbeeld is gericht op het demonstreren van de SocketActivityTrigger en niet de socket gebruikt voor andere activiteiten terwijl het wordt uitgevoerd. Uw app is waarschijnlijk complexer en moet OnSuspending gebruiken om te bepalen wanneer u TransferOwnership moet aanroepen.

Triggers voor controlekanalen

Zorg er eerst voor dat u besturingskanaaltriggers (CCT's) op de juiste manier gebruikt. Als u DatagramSocket-, StreamSocket- of StreamSocketListener-verbindingen gebruikt, raden we u aan SocketActivityTrigger te gebruiken. U kunt CCT's voor StreamSocket gebruiken, maar ze gebruiken meer resources en werken mogelijk niet in de modus Verbonden stand-by.

Als u WebSockets, IXMLHTTPRequest2, System.Net.Http.HttpClient of Windows gebruikt. Web.Http.HttpClient, moet u ControlChannelTrigger gebruiken.

ControlChannelTrigger met WebSockets

Important

De functie die in deze sectie wordt beschreven (ControlChannelTrigger met WebSockets) wordt ondersteund in Windows SDK-versie 10.0.15063.0 en hoger.

Enkele speciale overwegingen zijn van toepassing bij het gebruik van MessageWebSocket of StreamWebSocket met ControlChannelTrigger. Er zijn enkele transportspecifieke gebruikspatronen en aanbevolen procedures die moeten worden gevolgd bij het gebruik van een MessageWebSocket of StreamWebSocket met ControlChannelTrigger. Bovendien zijn deze overwegingen van invloed op de manier waarop aanvragen voor het ontvangen van pakketten op de StreamWebSocket worden verwerkt. Aanvragen voor het ontvangen van pakketten op de MessageWebSocket worden niet beïnvloed.

De volgende gebruikspatronen en aanbevolen procedures moeten worden gevolgd bij het gebruik van MessageWebSocket of StreamWebSocket met ControlChannelTrigger:

  • Een openstaande socket-ontvangst moet te allen tijde worden geplaatst. Dit is vereist om de pushmeldingstaken toe te staan.
  • Het WebSocket-protocol definieert een standaardmodel voor keep-alive-berichten. De WebSocketKeepAlive-klasse kan door de client geïnitieerde Keep Alive-berichten verzenden naar de server. De Klasse WebSocketKeepAlive moet door de app worden geregistreerd als TaskEntryPoint voor een KeepAliveTrigger.

Enkele speciale overwegingen zijn van invloed op de manier waarop aanvragen voor het ontvangen van pakketten in de StreamWebSocket worden verwerkt. Met name wanneer u een StreamWebSocket met ControlChannelTrigger gebruikt, moet uw app een raw async-patroon gebruiken voor het afhandelen van leesbewerkingen in plaats van het await-model in C# en VB.NET of taakobjecten in C++. Het onbewerkte asynchrone patroon wordt geïllustreerd in een codevoorbeeld verderop in deze sectie.

Met behulp van het ruwe asynchrone patroon kan Windows de methode IBackgroundTask.Run in de achtergrondtaak voor ControlChannelTrigger synchroniseren met het retourneren van de callback voor ontvangstvoltooiing. De Run-methode wordt aangeroepen nadat de voltooiingscallback is teruggekeerd. Dit zorgt ervoor dat de app de gegevens/fouten heeft ontvangen voordat de run-methode wordt aangeroepen.

Het is belangrijk op te merken dat de app nog een leesaanvraag moet uitvoeren voordat deze de controle vanuit de voltooiingscallback teruggeeft. Het is ook belangrijk om te weten dat de DataReader niet rechtstreeks kan worden gebruikt met het MessageWebSocket - of StreamWebSocket-transport , omdat de hierboven beschreven synchronisatie wordt verbroken. Het wordt niet ondersteund om de methode DataReader.LoadAsync rechtstreeks boven op het transport te gebruiken. In plaats daarvan kan de IBuffer die wordt geretourneerd door de methode IInputStream.ReadAsync in de eigenschap StreamWebSocket.InputStream later worden doorgegeven aan de methode DataReader.FromBuffer voor verdere verwerking.

In het volgende voorbeeld ziet u hoe u een onbewerkt asynchroon patroon gebruikt voor het verwerken van leesbewerkingen in de StreamWebSocket.

void PostSocketRead(int length)
{
    try
    {
        var readBuf = new Windows.Storage.Streams.Buffer((uint)length);
        var readOp = socket.InputStream.ReadAsync(readBuf, (uint)length, InputStreamOptions.Partial);
        readOp.Completed = (IAsyncOperationWithProgress<IBuffer, uint>
            asyncAction, AsyncStatus asyncStatus) =>
        {
            switch (asyncStatus)
            {
                case AsyncStatus.Completed:
                case AsyncStatus.Error:
                    try
                    {
                        // GetResults in AsyncStatus::Error is called as it throws a user friendly error string.
                        IBuffer localBuf = asyncAction.GetResults();
                        uint bytesRead = localBuf.Length;
                        readPacket = DataReader.FromBuffer(localBuf);
                        OnDataReadCompletion(bytesRead, readPacket);
                    }
                    catch (Exception exp)
                    {
                        Diag.DebugPrint("Read operation failed:  " + exp.Message);
                    }
                    break;
                case AsyncStatus.Canceled:

                    // Read is not cancelled in this sample.
                    break;
           }
       };
   }
   catch (Exception exp)
   {
       Diag.DebugPrint("failed to post a read failed with error:  " + exp.Message);
   }
}

De handler voor lezenvoltooiing wordt gegarandeerd geactiveerd voordat de methode IBackgroundTask.Run op de achtergrondtaak voor de ControlChannelTrigger wordt aangeroepen. Windows heeft interne synchronisatie om te wachten totdat een app terugkeert uit de read-completion-callback. De app verwerkt doorgaans snel de gegevens of de fout afkomstig van de MessageWebSocket of StreamWebSocket in de read completion-callback. Het bericht zelf wordt verwerkt in de context van de methode IBackgroundTask.Run . In dit voorbeeld hieronder wordt dit punt geïllustreerd met behulp van een berichtenwachtrij waarin de handler voor lezenvoltooiing het bericht invoegt en de achtergrondtaak later verwerkt.

In het volgende voorbeeld ziet u de handler voor leesvoltooiing die moet worden gebruikt met een onbewerkt asynchroon patroon voor het verwerken van leesbewerkingen in de StreamWebSocket.

public void OnDataReadCompletion(uint bytesRead, DataReader readPacket)
{
    if (readPacket == null)
    {
        Diag.DebugPrint("DataReader is null");

        // Ideally when read completion returns error,
        // apps should be resilient and try to
        // recover if there is an error by posting another recv
        // after creating a new transport, if required.
        return;
    }
    uint buffLen = readPacket.UnconsumedBufferLength;
    Diag.DebugPrint("bytesRead: " + bytesRead + ", unconsumedbufflength: " + buffLen);

    // check if buffLen is 0 and treat that as fatal error.
    if (buffLen == 0)
    {
        Diag.DebugPrint("Received zero bytes from the socket. Server must have closed the connection.");
        Diag.DebugPrint("Try disconnecting and reconnecting to the server");
        return;
    }

    // Perform minimal processing in the completion
    string message = readPacket.ReadString(buffLen);
    Diag.DebugPrint("Received Buffer : " + message);

    // Enqueue the message received to a queue that the push notify
    // task will pick up.
    AppContext.messageQueue.Enqueue(message);

    // Post another receive to ensure future push notifications.
    PostSocketRead(MAX_BUFFER_LENGTH);
}

Een extra detail voor Websockets is de keep-alive handler. Het WebSocket-protocol definieert een standaardmodel voor keep-alive-berichten.

Wanneer u MessageWebSocket of StreamWebSocket gebruikt, registreert u een exemplaar van de klasse WebSocketKeepAlive als TaskEntryPoint voor een KeepAliveTrigger, zodat de app uit de onderbroken status kan worden gehaald en periodiek keep-alive-berichten naar de server (extern eindpunt) kan verzenden. Dit moet worden gedaan als onderdeel van de app-code voor de achtergrondregistratie en in het pakketmanifest.

Dit taakinvoerpunt van Windows. Sockets.WebSocketKeepAlive moet op twee plaatsen worden opgegeven:

  • Wanneer u KeepAliveTrigger-trigger maakt in de broncode (zie het onderstaande voorbeeld).
  • In het app-pakketmanifest voor de declaratie van de keepalive-achtergrondtaak.

In het volgende voorbeeld wordt een netwerktriggermelding en een keepalive-trigger toegevoegd onder het <toepassingselement> in een app-manifest.

  <Extensions>
    <Extension Category="windows.backgroundTasks"
         Executable="$targetnametoken$.exe"
         EntryPoint="Background.PushNotifyTask">
      <BackgroundTasks>
        <Task Type="controlChannel" />
      </BackgroundTasks>
    </Extension>
    <Extension Category="windows.backgroundTasks"
         Executable="$targetnametoken$.exe"
         EntryPoint="Windows.Networking.Sockets.WebSocketKeepAlive">
      <BackgroundTasks>
        <Task Type="controlChannel" />
      </BackgroundTasks>
    </Extension>
  </Extensions>

Een app moet uiterst voorzichtig zijn wanneer u een await-instructie gebruikt in de context van een ControlChannelTrigger en een asynchrone bewerking op een StreamWebSocket, MessageWebSocket of StreamSocket. Een Task<bool>-object kan worden gebruikt om een ControlChannelTrigger te registreren voor pushmeldingen en WebSocket-keep-alives voor de StreamWebSocket en de transportverbinding tot stand te brengen. Als onderdeel van het registratieproces wordt StreamWebSocket ingesteld als transport voor de ControlChannelTrigger en wordt er een leesbewerking gestart. Met Task.Result wordt de huidige thread geblokkeerd totdat alle stappen in de taak worden uitgevoerd en instructies in de berichttekst worden geretourneerd. De taak is pas voltooid wanneer de methode true of false retourneert. Dit garandeert dat de hele methode wordt uitgevoerd. De taak kan meerdere wachtinstructies bevatten die worden beveiligd door de taak. Dit patroon moet worden gebruikt met het ControlChannelTrigger-object wanneer een StreamWebSocket of MessageWebSocket wordt gebruikt als het transport. Voor deze bewerkingen die lang kunnen duren (een typische asynchrone leesbewerking), moet de app bijvoorbeeld het onbewerkte asynchrone patroon gebruiken dat eerder is besproken.

In het volgende voorbeeld wordt ControlChannelTrigger geregistreerd voor pushmeldingen en WebSocket-keep-alives in de StreamWebSocket.

private bool RegisterWithControlChannelTrigger(string serverUri)
{
    // Make sure the objects are created in a system thread
    // Demonstrate the core registration path
    // Wait for the entire operation to complete before returning from this method.
    // The transport setup routine can be triggered by user control, by network state change
    // or by keepalive task
    Task<bool> registerTask = RegisterWithCCTHelper(serverUri);
    return registerTask.Result;
}

async Task<bool> RegisterWithCCTHelper(string serverUri)
{
    bool result = false;
    socket = new StreamWebSocket();

    // Specify the keepalive interval expected by the server for this app
    // in order of minutes.
    const int serverKeepAliveInterval = 30;

    // Specify the channelId string to differentiate this
    // channel instance from any other channel instance.
    // When background task fires, the channel object is provided
    // as context and the channel id can be used to adapt the behavior
    // of the app as required.
    const string channelId = "channelOne";

    // For websockets, the system does the keepalive on behalf of the app
    // But the app still needs to specify this well known keepalive task.
    // This should be done here in the background registration as well
    // as in the package manifest.
    const string WebSocketKeepAliveTask = "Windows.Networking.Sockets.WebSocketKeepAlive";

    // Try creating the controlchanneltrigger if this has not been already
    // created and stored in the property bag.
    ControlChannelTriggerStatus status;

    // Create the ControlChannelTrigger object and request a hardware slot for this app.
    // If the app is not on LockScreen, then the ControlChannelTrigger constructor will
    // fail right away.
    try
    {
        channel = new ControlChannelTrigger(channelId, serverKeepAliveInterval,
                                   ControlChannelTriggerResourceType.RequestHardwareSlot);
    }
    catch (UnauthorizedAccessException exp)
    {
        Diag.DebugPrint("Is the app on lockscreen? " + exp.Message);
        return result;
    }

    Uri serverUriInstance;
    try
    {
        serverUriInstance = new Uri(serverUri);
    }
    catch (Exception exp)
    {
        Diag.DebugPrint("Error creating URI: " + exp.Message);
        return result;
    }

    // Register the apps background task with the trigger for keepalive.
    var keepAliveBuilder = new BackgroundTaskBuilder();
    keepAliveBuilder.Name = "KeepaliveTaskForChannelOne";
    keepAliveBuilder.TaskEntryPoint = WebSocketKeepAliveTask;
    keepAliveBuilder.SetTrigger(channel.KeepAliveTrigger);
    keepAliveBuilder.Register();

    // Register the apps background task with the trigger for push notification task.
    var pushNotifyBuilder = new BackgroundTaskBuilder();
    pushNotifyBuilder.Name = "PushNotificationTaskForChannelOne";
    pushNotifyBuilder.TaskEntryPoint = "Background.PushNotifyTask";
    pushNotifyBuilder.SetTrigger(channel.PushNotificationTrigger);
    pushNotifyBuilder.Register();

    // Tie the transport method to the ControlChannelTrigger object to push enable it.
    // Note that if the transport' s TCP connection is broken at a later point of time,
    // the ControlChannelTrigger object can be reused to plug in a new transport by
    // calling UsingTransport API again.
    try
    {
        channel.UsingTransport(socket);

        // Connect the socket
        //
        // If connect fails or times out it will throw exception.
        // ConnectAsync can also fail if hardware slot was requested
        // but none are available
        await socket.ConnectAsync(serverUriInstance);

        // Call WaitForPushEnabled API to make sure the TCP connection has
        // been established, which will mean that the OS will have allocated
        // any hardware slot for this TCP connection.
        //
        // In this sample, the ControlChannelTrigger object was created by
        // explicitly requesting a hardware slot.
        //
        // On systems that without connected standby, if app requests hardware slot as above,
        // the system will fallback to a software slot automatically.
        //
        // On systems that support connected standby,, if no hardware slot is available, then app
        // can request a software slot by re-creating the ControlChannelTrigger object.
        status = channel.WaitForPushEnabled();
        if (status != ControlChannelTriggerStatus.HardwareSlotAllocated
            && status != ControlChannelTriggerStatus.SoftwareSlotAllocated)
        {
            throw new Exception(string.Format("Neither hardware nor software slot could be allocated. ChannelStatus is {0}", status.ToString()));
        }

        // Store the objects created in the property bag for later use.
        CoreApplication.Properties.Remove(channel.ControlChannelTriggerId);

        var appContext = new AppContext(this, socket, channel, channel.ControlChannelTriggerId);
        ((IDictionary<string, object>)CoreApplication.Properties).Add(channel.ControlChannelTriggerId, appContext);
        result = true;

        // Almost done. Post a read since we are using streamwebsocket
        // to allow push notifications to be received.
        PostSocketRead(MAX_BUFFER_LENGTH);
    }
    catch (Exception exp)
    {
         Diag.DebugPrint("RegisterWithCCTHelper Task failed with: " + exp.Message);

         // Exceptions may be thrown for example if the application has not
         // registered the background task class id for using real time communications
         // broker in the package manifest.
    }
    return result;
}

Zie het voorbeeld van ControlChannelTrigger StreamWebSocket voor meer informatie over het gebruik van MessageWebSocket of StreamWebSocket met ControlChannelTrigger.

ControlChannelTrigger met HttpClient

Enkele speciale overwegingen zijn van toepassing bij het gebruik van HttpClient met ControlChannelTrigger. Er zijn enkele transportspecifieke gebruikspatronen en aanbevolen procedures die moeten worden gevolgd bij het gebruik van een HttpClient met ControlChannelTrigger. Bovendien zijn deze overwegingen van invloed op de manier waarop aanvragen voor het ontvangen van pakketten op de HttpClient worden verwerkt.

OpmerkingHttpClient met SSL wordt momenteel niet ondersteund bij gebruik van de netwerktriggerfunctie en ControlChannelTrigger.   De volgende gebruikspatronen en aanbevolen procedures moeten worden gevolgd bij het gebruik van HttpClient met ControlChannelTrigger:

  • De app moet mogelijk verschillende eigenschappen en headers instellen op het object HttpClient of HttpClientHandler in de system.Net.Http-naamruimte voordat de aanvraag naar de specifieke URI wordt verzonden.
  • Een app moet mogelijk een eerste aanvraag indienen om het transport goed te testen en in te stellen voordat u het HttpClient-transport maakt dat moet worden gebruikt met ControlChannelTrigger. Zodra de app bepaalt dat het transport correct kan worden ingesteld, kan een HttpClient-object worden geconfigureerd als het transportobject dat wordt gebruikt met het Object ControlChannelTrigger . Dit proces is ontworpen om te voorkomen dat sommige scenario's de verbinding verbreken die via het transport tot stand is gebracht. Als u SSL gebruikt met een certificaat, moet voor een app mogelijk een dialoogvenster worden weergegeven voor pincodevermelding of als er meerdere certificaten zijn waaruit u kunt kiezen. Proxyverificatie en serververificatie zijn mogelijk vereist. Als de proxy- of serververificatie verloopt, kan de verbinding worden gesloten. Een manier waarop een app deze problemen met het verlopen van verificatie kan afhandelen, is door een timer in te stellen. Wanneer een HTTP-omleiding is vereist, is het niet gegarandeerd dat de tweede verbinding betrouwbaar tot stand kan worden gebracht. Met een eerste testaanvraag wordt gewaarborgd dat de app de meest recente omleidings-URL kan gebruiken voordat het object HttpClient als transport wordt gebruikt met het object ControlChannelTrigger.

In tegenstelling tot andere netwerktransporten kan het HttpClient-object niet rechtstreeks worden doorgegeven aan de UsingTransport-methode van het Object ControlChannelTrigger . In plaats daarvan moet een HttpRequestMessage-object speciaal worden samengesteld voor gebruik met het HttpClient-object en de ControlChannelTrigger. Het HttpRequestMessage-object wordt gemaakt met behulp van de methode RtcRequestFactory.Create . Het HttpRequestMessage-object dat wordt gemaakt, wordt vervolgens doorgegeven aan de methode UsingTransport .

In het volgende voorbeeld ziet u hoe u een HttpRequestMessage-object maakt voor gebruik met het HttpClient-object en de ControlChannelTrigger.

using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Windows.Networking.Sockets;

public HttpRequestMessage httpRequest;
public HttpClient httpClient;
public HttpRequestMessage httpRequest;
public ControlChannelTrigger channel;
public Uri serverUri;

private void SetupHttpRequestAndSendToHttpServer()
{
    try
    {
        // For HTTP based transports that use the RTC broker, whenever we send next request, we will abort the earlier
        // outstanding http request and start new one.
        // For example in case when http server is taking longer to reply, and keep alive trigger is fired in-between
        // then keep alive task will abort outstanding http request and start a new request which should be finished
        // before next keep alive task is triggered.
        if (httpRequest != null)
        {
            httpRequest.Dispose();
        }

        httpRequest = RtcRequestFactory.Create(HttpMethod.Get, serverUri);

        SendHttpRequest();
    }
        catch (Exception e)
    {
        Diag.DebugPrint("Connect failed with: " + e.ToString());
        throw;
    }
}

Enkele speciale overwegingen zijn van invloed op de manier waarop aanvragen voor het verzenden van HTTP-aanvragen op de HttpClient om het ontvangen van een antwoord te initiëren, worden verwerkt. Met name wanneer u een HttpClient gebruikt met ControlChannelTrigger, moet uw app een taak gebruiken voor het verwerken van verzendingen in plaats van het await-model .

Met HttpClient is er geen synchronisatie met de methode IBackgroundTask.Run op de achtergrondtaak voor de ControlChannelTrigger met de retournering van de callback voor ontvangstvoltooiing. Daarom kan de app alleen de blokkerende HttpResponseMessage-techniek in de run-methode gebruiken en wachten totdat het hele antwoord is ontvangen.

Het gebruik van HttpClient met ControlChannelTrigger verschilt aanzienlijk van de StreamSocket-, MessageWebSocket - of StreamWebSocket-transporten . De callback voor ontvangst van HttpClient wordt via een Task aan de app geleverd, omdat dit via de HttpClient-code verloopt. Dit betekent dat de pushmeldingstaak ControlChannelTrigger wordt geactiveerd zodra de gegevens of fout naar de app worden verzonden. In het onderstaande voorbeeld slaat de code de responseTask op die is geretourneerd door de methode HttpClient.SendAsync in globale opslag die de pushmeldingstaak inline ophaalt en verwerkt.

In het volgende voorbeeld ziet u hoe u verzendaanvragen op de HttpClient kunt verwerken wanneer deze worden gebruikt met ControlChannelTrigger.

using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Windows.Networking.Sockets;

private void SendHttpRequest()
{
    if (httpRequest == null)
    {
        throw new Exception("HttpRequest object is null");
    }

    // Tie the transport method to the controlchanneltrigger object to push enable it.
    // Note that if the transport' s TCP connection is broken at a later point of time,
    // the controlchanneltrigger object can be reused to plugin a new transport by
    // calling UsingTransport API again.
    channel.UsingTransport(httpRequest);

    // Call the SendAsync function to kick start the TCP connection establishment
    // process for this http request.
    Task<HttpResponseMessage> httpResponseTask = httpClient.SendAsync(httpRequest);

    // Call WaitForPushEnabled API to make sure the TCP connection has been established,
    // which will mean that the OS will have allocated any hardware slot for this TCP connection.
    ControlChannelTriggerStatus status = channel.WaitForPushEnabled();
    Diag.DebugPrint("WaitForPushEnabled() completed with status: " + status);
    if (status != ControlChannelTriggerStatus.HardwareSlotAllocated
        && status != ControlChannelTriggerStatus.SoftwareSlotAllocated)
    {
        throw new Exception("Hardware/Software slot not allocated");
    }

    // The HttpClient receive callback is delivered via a Task to the app.
    // The notification task will fire as soon as the data or error is dispatched
    // Enqueue the responseTask returned by httpClient.sendAsync
    // into a queue that the push notify task will pick up and process inline.
    AppContext.messageQueue.Enqueue(httpResponseTask);
}

In het volgende voorbeeld ziet u hoe u reacties leest die zijn ontvangen op de HttpClient wanneer deze wordt gebruikt met ControlChannelTrigger.

using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

public string ReadResponse(Task<HttpResponseMessage> httpResponseTask)
{
    string message = null;
    try
    {
        if (httpResponseTask.IsCanceled || httpResponseTask.IsFaulted)
        {
            Diag.DebugPrint("Task is cancelled or has failed");
            return message;
        }
        // We' ll wait until we got the whole response.
        // This is the only supported scenario for HttpClient for ControlChannelTrigger.
        HttpResponseMessage httpResponse = httpResponseTask.Result;
        if (httpResponse == null || httpResponse.Content == null)
        {
            Diag.DebugPrint("Cannot read from httpresponse, as either httpResponse or its content is null. try to reset connection.");
        }
        else
        {
            // This is likely being processed in the context of a background task and so
            // synchronously read the Content' s results inline so that the Toast can be shown.
            // before we exit the Run method.
            message = httpResponse.Content.ReadAsStringAsync().Result;
        }
    }
    catch (Exception exp)
    {
        Diag.DebugPrint("Failed to read from httpresponse with error:  " + exp.ToString());
    }
    return message;
}

Zie het ControlChannelTrigger HttpClient-voorbeeld voor meer informatie over het gebruik van HttpClient met ControlChannelTrigger.

ControlChannelTrigger met IXMLHttpRequest2

Enkele speciale overwegingen zijn van toepassing bij het gebruik van IXMLHTTPRequest2 met ControlChannelTrigger. Er zijn enkele transportspecifieke gebruikspatronen en aanbevolen procedures die moeten worden gevolgd bij het gebruik van een IXMLHTTPRequest2 met ControlChannelTrigger. Het gebruik van ControlChannelTrigger heeft geen invloed op de manier waarop aanvragen voor het verzenden of ontvangen van HTTP-aanvragen op de IXMLHTTPRequest2 worden verwerkt.

Gebruikspatronen en aanbevolen procedures bij het gebruik van IXMLHTTPRequest2 met ControlChannelTrigger

  • Een IXMLHTTPRequest2-object dat als transport wordt gebruikt, heeft een levensduur van slechts één aanvraag/antwoordpaar. Wanneer u het object ControlChannelTrigger gebruikt, is het handig om het Object ControlChannelTrigger eenmaal te maken en in te stellen en vervolgens de UsingTransport-methode herhaaldelijk aan te roepen, telkens wanneer u een nieuw IXMLHTTPRequest2-object aanroept. Een app moet het vorige IXMLHTTPRequest2-object verwijderen voordat een nieuw IXMLHTTPRequest2-object wordt opgegeven om ervoor te zorgen dat de app de toegewezen resourcelimieten niet overschrijdt.
  • De app moet mogelijk de methoden SetProperty en SetRequestHeader aanroepen om het HTTP-transport in te stellen voordat de verzendmethode wordt aangeroepen.
  • Een app moet mogelijk eerst een initiële Send-aanvraag doen om het transport correct te testen en in te stellen voordat de app het transport maakt voor gebruik met ControlChannelTrigger. Zodra de app vaststelt dat het transport correct is ingesteld, kan het object IXMLHTTPRequest2 worden geconfigureerd als het transportobject dat wordt gebruikt met ControlChannelTrigger. Dit proces is ontworpen om te voorkomen dat sommige scenario's de verbinding verbreken die via het transport tot stand is gebracht. Als u SSL gebruikt met een certificaat, moet voor een app mogelijk een dialoogvenster worden weergegeven voor pincodevermelding of als er meerdere certificaten zijn waaruit u kunt kiezen. Proxyverificatie en serververificatie zijn mogelijk vereist. Als de proxy- of serververificatie verloopt, kan de verbinding worden gesloten. Een manier waarop een app deze problemen met het verlopen van verificatie kan afhandelen, is door een timer in te stellen. Wanneer een HTTP-omleiding is vereist, is het niet gegarandeerd dat de tweede verbinding betrouwbaar tot stand kan worden gebracht. Een eerste testverzoek zorgt ervoor dat de app de meest actuele omgeleide URL kan gebruiken voordat de app het object IXMLHTTPRequest2 als transport gebruikt met het object ControlChannelTrigger.

Zie voor meer informatie over het gebruik van IXMLHTTPRequest2 met ControlChannelTrigger het voorbeeld ControlChannelTrigger met IXMLHTTPRequest2.

Belangrijke API's