Comunicazioni di rete in background

Per continuare la comunicazione di rete anche se non è in primo piano, l'app può usare le attività in background e una di queste due opzioni.

  • Gestore di socket. Le app che usano i socket per connessioni a lungo termine possono delegare la proprietà di un socket a un gestore di socket di sistema quando lasciano il primo piano. Il gestore attiva quindi l'app quando il traffico arriva al socket, ritrasferisce la proprietà all'app e l'app elabora il traffico in arrivo.
  • Trigger canale di controllo.

Esecuzione di operazioni di rete nelle attività in background

  • Usare una classe SocketActivityTrigger per attivare l'attività in background quando viene ricevuto un pacchetto ed è necessario eseguire un'attività di breve durata. Dopo aver eseguito l'attività, l'attività in background dovrebbe terminare per risparmiare energia.
  • Usare una classe ControlChannelTrigger per attivare l'attività in background quando viene ricevuto un pacchetto ed è necessario eseguire un'attività di lunga durata.

Flag e condizioni correlate alla rete

  • Aggiungere la condizione InternetAvailable all'attività in background BackgroundTaskBuilder.AddCondition per ritardare l'attivazione dell'attività in background finché lo stack di rete è in esecuzione. Questa condizione consente di risparmiare energia perché l'attività in background non verrà eseguita finché la rete è attiva. Questa condizione non fornisce l'attivazione in tempo reale.

Indipendentemente dal trigger usato, impostare IsNetworkRequested per la tua attività in background per assicurarsi che la rete rimanga attiva durante l'esecuzione dell'attività in background. In questo modo, l'infrastruttura delle attività in background mantiene sempre attiva la rete mentre l'attività è in esecuzione, anche se il dispositivo si trova in modalità Standby connesso. Se l'attività in background non usa IsNetworkRequested, l'attività in background non sarà in grado di accedere alla rete se è attiva la modalità Standby connesso (ad esempio, quando lo schermo del telefono è spento).

Gestore del socket e SocketActivityTrigger

Se l'app usa connessioni DatagramSocket, StreamSocket o StreamSocketListener, occorre usare SocketActivityTrigger e il gestore del socket per ricevere una notifica quando arriva traffico per l'app mentre non è in primo piano.

Per ricevere ed elaborare i dati ricevuti su un socket mentre non è attiva, l'app deve eseguire una sola volta la configurazione all'avvio e quindi trasferire la proprietà del socket al gestore del socket mentre passa a uno stato in cui non è attiva.

I passaggi per la configurazione singola includono la creazione di un trigger, la registrazione di un'attività in background per il trigger e l'abilitazione del socket per il gestore del socket:

  • Creare un SocketActivityTrigger e registrare un'attività in background per il trigger con il parametro TaskEntryPoint impostato sul codice per elaborare un pacchetto ricevuto.
            var socketTaskBuilder = new BackgroundTaskBuilder();
            socketTaskBuilder.Name = _backgroundTaskName;
            socketTaskBuilder.TaskEntryPoint = _backgroundTaskEntryPoint;
            var trigger = new SocketActivityTrigger();
            socketTaskBuilder.SetTrigger(trigger);
            _task = socketTaskBuilder.Register();
  • Chiamare EnableTransferOwnership sul socket prima di eseguire il binding del socket.
           _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");

Dopo aver correttamente configurato il socket, nel momento in cui l'app sta per essere sospesa, chiamare TransferOwnership sul socket per trasferirlo a un gestore del socket. Il gestore monitora il socket e attiva l'attività in background nel momento in cui vengono ricevuti i dati. L'esempio seguente include una funzione di utilità TransferOwnership per eseguire il trasferimento per i socket StreamSocketListener. Notare che i diversi tipi di socket hanno ciascuno il proprio metodo TransferOwnership, quindi è necessario chiamare il metodo appropriato per il socket di cui si sta trasferendo la proprietà. Il codice probabilmente conterrà un helper TransferOwnership sovraccarico con un'implementazione per ogni tipo di socket usato, in modo che il codice OnSuspending continui a essere facile da leggere.

Un'app trasferisce la proprietà di un socket a un gestore e passa l'ID per l'attività in background usando il metodo appropriato tra i seguenti:


// declare int _transferOwnershipCount as a field.

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

Nel gestore eventi dell'attività in background:

  • Prima di tutto, ottenere un rinvio dell'attività in background per poter gestire l'evento con i metodi asincroni.
var deferral = taskInstance.GetDeferral();
  • Estrarre quindi SocketActivityTriggerDetails dagli argomenti dell'evento e trovare il motivo per cui l'evento è stato generato:
var details = taskInstance.TriggerDetails as SocketActivityTriggerDetails;
    var socketInformation = details.SocketInformation;
    switch (details.Reason)
  • Se l'evento è stato generato in seguito all'attività del socket, creare un DataReader nel socket, caricare il lettore in modo asincrono e quindi usare i dati in base alla progettazione dell'app. Notare che occorre restituire la proprietà del socket al gestore per poter ricevere nuovamente una notifica dell'ulteriore attività del socket.

Nell'esempio seguente il testo ricevuto nel socket viene visualizzato in un avviso popup.

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;
  • Se l'evento è stato generato perché è scaduto un timer keep-alive, il codice dovrebbe inviare alcuni dati tramite il socket per mantenere il socket attivo e riavviare il timer keep-alive. Ricordare che è importante restituire la proprietà del socket al gestore per ricevere altre notifiche sugli eventi:
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;
  • Se l'evento è stato generato perché il socket è stato chiuso, ristabilire il socket assicurandosi di trasferire la proprietà del nuovo socket al gestore dopo averlo creato. In questo esempio il nome host e la porta vengono archiviati nelle impostazioni locali in modo che sia possibile usarli per stabilire una nuova connessione socket:
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;
  • Non dimenticare di completare il rinvio dopo avere terminato di elaborare la notifica dell'evento:
  deferral.Complete();

Per un esempio completo dell'uso di SocketActivityTrigger e del gestore del socket, vedere l'esempio SocketActivityStreamSocket. L'inizializzazione del socket viene eseguita in Scenario1_Connect.xaml.cs e l'implementazione dell'attività in background è in SocketActivityTask.cs.

Probabilmente si noterà che l'esempio chiama TransferOwnership non appena crea un nuovo socket o acquisisce un socket esistente anziché usare il gestore eventi OnSuspending come descritto in questo argomento. Infatti l'esempio ha lo scopo di illustrare SocketActivityTrigger e quindi non usa il socket per altre attività mentre è in esecuzione. L'app reale sarà probabilmente più complessa e dovrà usare OnSuspending per determinare quando chiamare TransferOwnership.

Trigger canale di controllo

Per prima cosa assicurarsi di usare correttamente i trigger canale di controllo (Control Channel Trigger, CCT). Se si stanno usando connessioni DatagramSocket, StreamSocket o StreamSocketListener, si consiglia di usare SocketActivityTrigger. È possibile usare i trigger CCT per StreamSocket, ma usano più risorse e potrebbero non funzionare in modalità di standby connesso.

Se si usa WebSocket, IXMLHTTPRequest2, System.Net.Http.HttpClient o Windows.Web.Http.HttpClient, usare ControlChannelTrigger.

ControlChannelTrigger con WebSocket

Importante

La funzionalità descritta in questa sezione (ControlChannelTrigger with WebSockets) è supportata nella versione 10.0.15063.0 dell'SDK e nelle versioni precedenti. È inoltre supportata nelle versioni non definitive di Windows 10 Insider Preview.

Quando si usa MessageWebSocket o StreamWebSocket con ControlChannelTrigger è necessario tener conto di alcune considerazioni particolari. Esistono alcuni modelli di utilizzo e procedure consigliate specifiche per il trasporto da seguire quando si usa MessageWebSocket o StreamWebSocket con ControlChannelTrigger. Queste considerazioni riguardano anche le modalità con cui vengono gestite le richieste di ricezione dei pacchetti su StreamWebSocket. Non riguardano invece le richieste di ricezione di pacchetti su MessageWebSocket.

Quando si usa MessageWebSocket o StreamWebSocket con ControlChannelTrigger è necessario attenersi ai modelli di utilizzo e alle procedure consigliate seguenti:

  • Deve essere sempre inserito un socket di ricezione in attesa, necessario per consentire l'esecuzione delle attività di notifica push.
  • Il protocollo WebSocket definisce un modello standard per i messaggi keep-alive. La classe WebSocketKeepAlive può inviare al server messaggi keep-alive del protocollo WebSocket avviati dal client. L'app dovrebbe registrare la classe WebSocketKeepAlive come TaskEntryPoint di KeepAliveTrigger.

Alcune considerazioni speciali riguardano il modo in cui vengono gestite le richieste di ricezione dei pacchetti su StreamWebSocket. In particolare, se usa StreamWebSocket con ControlChannelTrigger, per elaborare le operazioni di lettura l'app deve applicare un modello asincrono non elaborato e non il modello await in C# e VB.NET o Tasks in C++. Il modello asincrono non elaborato è illustrato in un esempio di codice più avanti in questa sezione.

L'uso del modello asincrono non elaborato consente a Windows di sincronizzare il metodo IBackgroundTask.Run nell'attività in background per ControlChannelTrigger quando restituisce il callback di completamento della ricezione. Il metodo Run viene richiamato dopo la restituzione del callback di completamento. In questo modo si garantisce che l'app abbia ricevuto i dati o gli errori prima che venga richiamato il metodo Run.

È importante notare che l'app deve inserire un'altra operazione di lettura prima di restituire il controllo dopo il callback di completamento. Va inoltre sottolineato che DataReader non può essere usato direttamente con il trasporto MessageWebSocket o StreamWebSocket perché questo interromperebbe la sincronizzazione sopra descritta. Non è supportato l'uso diretto del metodo DataReader.LoadAsync sul trasporto. L'IBuffer restituito dal metodo IInputStream.ReadAsync sulla proprietà StreamWebSocket.InputStream può invece essere successivamente passato al metodo DataReader.FromBuffer per l'ulteriore elaborazione.

L'esempio seguente mostra come usare un modello asincrono non elaborato per gestire le operazioni di lettura su 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);
   }
}

Il gestore di completamento della lettura viene sicuramente generato prima che venga richiamato il metodo IBackgroundTask.Run nell'attività in background per ControlChannelTrigger. Windows ha una sincronizzazione interna per attendere la restituzione dell'app dal callback di completamento della lettura. L'app in genere elabora velocemente i dati o l'errore generati da MessageWebSocket o StreamWebSocket nel callback di completamento della lettura. Il messaggio stesso viene elaborato nel contesto del metodo IBackgroundTask.Run. Nell'esempio seguente questo punto è illustrato usando una coda di messaggi in cui il gestore di completamento della lettura inserisce il messaggio e l'attività in background viene elaborata successivamente.

L'esempio seguente mostra il gestore di completamento della lettura da usare con un modello asincrono non elaborato per gestire le letture su 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);
}

Un dettaglio aggiuntivo per Websockets è il gestore keep-alive. Il protocollo WebSocket definisce un modello standard per i messaggi keep-alive.

Quando si usa MessageWebSocket o StreamWebSocket, registrare un'istanza della classe WebSocketKeepAlive come TaskEntryPoint affinché un KeepAliveTrigger consenta all'app di annullare la sospensione e inviare messaggi keep-alive al server (endpoint remoto) periodicamente. L'operazione deve essere eseguita sia nel codice dell'app per la registrazione in background che nel manifesto del pacchetto.

Questo punto di ingresso dell'attività di Windows.Sockets.WebSocketKeepAlive va specificato due volte:

  • Quando si crea il trigger KeepAliveTrigger nel codice sorgente (vedere l'esempio seguente).
  • Nel manifesto del pacchetto dell'app per la dichiarazione dell'attività in background keep-alive.

L'esempio seguente aggiunge una notifica trigger di rete e un trigger keep-alive sotto l'elemento <Application> nel manifesto di un'app.

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

In un'app occorre prestare molta attenzione quando si usa un'istruzione await nel contesto di un oggetto ControlChannelTrigger e si esegue un'operazione asincrona su un oggetto StreamWebSocket, MessageWebSocket o StreamSocket. È possibile usare un oggetto Task<bool> per registrare un ControlChannelTrigger per la notifica push e i keep-alive WebSocket su StreamWebSocket e quindi connettere il trasporto. Durante la registrazione StreamWebSocket viene impostato come trasporto per ControlChannelTrigger e viene richiesta una lettura. Task.Result mantiene bloccato il thread corrente finché tutti i passaggi dell'attività non vengono eseguiti e restituisce le istruzioni nel corpo del messaggio. L'attività non viene risolta finché il metodo non restituisce True o False, per garantire che sia stato eseguito completamente. L'oggetto Task può contenere più istruzioni await protette da Task. Questo modello deve essere usato con l'oggetto ControlChannelTrigger quando il trasporto è costituito da StreamWebSocket o MessageWebSocket. Per le operazioni che possono richiedere molto tempo, come una tipica operazione di lettura asincrona, l'app dovrebbe usare il modello asincrono non elaborato illustrato in precedenza.

L'esempio seguente registra ControlChannelTrigger per la notifica push e i keep-alive WebSocket su 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
}

Per altre informazioni sull'uso di MessageWebSocket o StreamWebSocket con ControlChannelTrigger, vedere l'esempio ControlChannelTrigger StreamWebSocket.

ControlChannelTrigger con HttpClient

Quando si usa HttpClient con ControlChannelTrigger è necessario tener conto di alcune considerazioni particolari. Esistono alcuni modelli di utilizzo e procedure consigliate specifiche per il trasporto da seguire quando si usa HttpClient con ControlChannelTrigger. Queste considerazioni riguardano anche le modalità con cui vengono gestite le richieste di ricezione dei pacchetti su HttpClient.

NotaHttpClient con SSL non è attualmente supportato quando si usa la funzionalità trigger di rete e ControlChannelTrigger.   Quando si usa HttpClient con ControlChannelTrigger è necessario attenersi ai modelli di utilizzo e alle procedure consigliate seguenti:

  • È possibile che l'app debba impostare varie proprietà e intestazioni nell'oggetto HttpClient o HttpClientHandler dello spazio dei nomi System.Net.Http prima di inviare la richiesta all'URI specifico.
  • È possibile che l'app debba effettuare una richiesta iniziale per testare e impostare correttamente il trasporto prima di creare il trasporto HttpClient da usare con ControlChannelTrigger. Dopo che l'app ha stabilito che il trasporto può essere impostato correttamente, è possibile configurare un oggetto HttpClient come l'oggetto di trasporto usato con l'oggetto ControlChannelTrigger. La procedura è stata concepita per impedire ad alcuni scenari di provocare l'interruzione della connessione stabilita durante il trasporto. Se si usa SSL con un certificato, è possibile che l'app richieda la visualizzazione di una finestra di dialogo in cui immettere il PIN o di scegliere un certificato nel caso in cui ce ne fosse più di uno. Può essere necessaria l'autenticazione del proxy e del server. Se l'autenticazione del proxy o del server scade, è possibile che la connessione venga chiusa. Uno dei modi per risolvere questi problemi di scadenza dell'autenticazione è impostare un timer. Se è necessario un reindirizzamento HTTP, non vi è alcuna garanzia che possa essere stabilita una seconda connessione affidabile. Una richiesta di test iniziale garantisce che l'app possa usare l'URL reindirizzato più aggiornato prima di usare l'oggetto HttpClient come trasporto con l'oggetto ControlChannelTrigger.

Diversamente da altri trasporti di rete, l'oggetto HttpClient non può essere passato direttamente nel metodo UsingTransport dell'oggetto ControlChannelTrigger. È invece necessario creare specificamente un oggetto HttpRequestMessage da usare con l'oggetto HttpClient e con ControlChannelTrigger. L'oggetto HttpRequestMessage viene creato mediante il metodo RtcRequestFactory.Create. L'oggetto HttpRequestMessage creato viene quindi passato al metodo UsingTransport.

L'esempio seguente mostra come creare un oggetto HttpRequestMessage da usare con l'oggetto HttpClient e con 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;
    }
}

Alcune considerazioni speciali riguardano il modo in cui vengono gestite le richieste di inviare richieste HTTP su HttpClient per avviare la ricezione di una risposta. In particolare, quando usa un HttpClient con ControlChannelTrigger l'app deve usare un Task per gestire gli invii, invece del modello await.

Se si usa HttpClient non avviene la sincronizzazione con il metodo IBackgroundTask.Run nell'attività in background per ControlChannelTrigger con la restituzione del callback di completamento della ricezione. Per questo motivo l'app può usare solo la tecnica di blocco HttpResponseMessage nel metodo Run e attendere la ricezione dell'intera risposta.

L'uso di HttpClient con ControlChannelTrigger differisce notevolmente dai trasporti StreamSocket, MessageWebSocket o StreamWebSocket. Il callback di ricezione di HttpClient viene recapitato all'app tramite un Task mediante il codice HttpClient. Ciò significa che l'attività di notifica push di ControlChannelTrigger viene generata non appena i dati o l'errore vengono recapitati all'app. Nell'esempio seguente, il codice memorizza nell'archivio globale il Task di risposta restituito dal metodo HttpClient.SendAsync che l'attività di notifica push selezionerà ed elaborerà inline.

L'esempio seguente mostra come gestire le richieste di invio su HttpClient quando è usato con 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);
}

L'esempio seguente illustra come leggere le risposte ricevute su HttpClient quando è usato con 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;
}

Per altre informazioni sull'uso di HttpClient con ControlChannelTrigger, vedere l'esempio ControlChannelTrigger HttpClient.

ControlChannelTrigger con IXMLHttpRequest2

Quando si usa IXMLHTTPRequest2 con ControlChannelTrigger è necessario tener conto di alcune considerazioni particolari. Esistono alcuni modelli di utilizzo e procedure consigliate specifiche per il trasporto da seguire quando si usa IXMLHTTPRequest2 con ControlChannelTrigger. L'uso di ControlChannelTrigger non influisce sul modo in cui vengono gestite le richieste di invio o ricezione di richieste HTTP su IXMLHTTPRequest2.

Modelli di utilizzo e procedure consigliate per l'uso di IXMLHTTPRequest2 con ControlChannelTrigger

  • Se usato come trasporto, un oggetto IXMLHTTPRequest2 ha una durata limitata a una richiesta/risposta. Se usato con l'oggetto ControlChannelTrigger, conviene creare e impostare l'oggetto ControlChannelTrigger una sola volta, quindi chiamare ripetutamente il metodo UsingTransport associandovi ogni volta un nuovo oggetto IXMLHTTPRequest2. L'app dovrebbe eliminare il precedente oggetto IXMLHTTPRequest2 prima di fornire un nuovo oggetto IXMLHTTPRequest2 per non rischiare di superare i limiti di risorse allocate.
  • È possibile che l'app debba chiamare i metodi SetProperty e SetRequestHeader per impostare il trasporto HTTP prima di chiamare il metodo Send.
  • È possibile che l'app debba effettuare una richiesta Send iniziale per testare e impostare correttamente il trasporto prima di creare il trasporto da usare con ControlChannelTrigger. Una volta che l'app ha stabilito che il trasporto è stato impostato correttamente, l'oggetto IXMLHTTPRequest2 può essere configurato come oggetto di trasporto usato con ControlChannelTrigger. La procedura è stata concepita per impedire ad alcuni scenari di provocare l'interruzione della connessione stabilita durante il trasporto. Se si usa SSL con un certificato, è possibile che l'app richieda la visualizzazione di una finestra di dialogo in cui immettere il PIN o di scegliere un certificato nel caso in cui ce ne fosse più di uno. Può essere necessaria l'autenticazione del proxy e del server. Se l'autenticazione del proxy o del server scade, è possibile che la connessione venga chiusa. Uno dei modi per risolvere questi problemi di scadenza dell'autenticazione è impostare un timer. Se è necessario un reindirizzamento HTTP, non vi è alcuna garanzia che possa essere stabilita una seconda connessione affidabile. Una richiesta di test iniziale garantisce all'app l'uso dell'URL reindirizzato più aggiornato prima di usare l'oggetto IXMLHTTPRequest2 come trasporto con l'oggetto ControlChannelTrigger.

Per altre informazioni sull'uso di IXMLHTTPRequest2 con ControlChannelTrigger, vedere l'esempio ControlChannelTrigger con IXMLHTTPRequest2.

API importanti