Comunicações de rede em segundo plano

Para continuar a comunicação em rede enquanto não está em primeiro plano, a sua aplicação pode usar tarefas em segundo plano e uma destas duas opções.

  • Corretor de soquete. Se a sua aplicação usar sockets para ligações de longa duração, então, quando deixar de estar em primeiro plano, pode delegar a propriedade de um socket a um intermediário de sockets do sistema. O intermediário então: ativa a sua aplicação quando o tráfego chega ao socket; transfere o controlo de volta para a sua aplicação; e a sua aplicação processa então o tráfego recebido.
  • Acionadores do canal de controlo.

Execução de operações de rede em tarefas em segundo plano

  • Use um SocketActivityTrigger para ativar a tarefa em segundo plano quando um pacote for recebido e precisar de realizar uma tarefa de curta duração. Após a realização da tarefa, a tarefa em segundo plano deve terminar para poupar energia.
  • Use um ControlChannelTrigger para ativar a tarefa em segundo plano quando um pacote for recebido e precisar de realizar uma tarefa de longa duração.

Condições e flags relacionados com a rede

  • Adicione a condição InternetAvailable à sua tarefa em segundo plano BackgroundTaskBuilder.AddCondition para atrasar o disparo da tarefa em segundo plano até que a pilha de rede esteja a correr. Esta condição poupa energia porque a tarefa em segundo plano não será executada até a rede estar ativa. Esta condição não proporciona ativação em tempo real.

Independentemente do gatilho que usar, defina o IsNetworkRequested na tarefa em segundo plano para garantir que a rede permanece ativa enquanto a tarefa em segundo plano está a correr. Isto indica à infraestrutura de tarefas em segundo plano para manter a rede ativa enquanto a tarefa está a ser executada, mesmo que o dispositivo tenha entrado em modo de Espera Conectado. Se a sua tarefa em segundo plano não usar o IsNetworkRequested, então a tarefa em segundo plano não poderá aceder à rede quando estiver em modo Connected Standby (por exemplo, quando o ecrã do telemóvel está desligado).

Corretor de sockets e o SocketActivityTrigger

Se a sua aplicação usar as ligações DatagramSocket, StreamSocket ou StreamSocketListener, então deve usar SocketActivityTrigger e o mediador de sockets para ser notificado quando houver tráfego para a sua aplicação enquanto esta não estiver em primeiro plano.

Para que a sua aplicação receba e processe os dados recebidos num socket quando a sua app não está ativa, a aplicação deve realizar uma configuração única no arranque e depois transferir a propriedade do socket para o socket broker quando estiver a transitar para um estado onde não está ativa.

Os passos de configuração inicial, efetuados uma única vez, consistem em criar um accionador, registar uma tarefa em segundo plano para esse accionador e activar o socket para o intermediário de sockets:

  • Cria um SocketActivityTrigger e regista uma tarefa em segundo plano para o trigger com o parâmetro TaskEntryPoint definido no teu código para processar um pacote recebido.
            var socketTaskBuilder = new BackgroundTaskBuilder();
            socketTaskBuilder.Name = _backgroundTaskName;
            socketTaskBuilder.TaskEntryPoint = _backgroundTaskEntryPoint;
            var trigger = new SocketActivityTrigger();
            socketTaskBuilder.SetTrigger(trigger);
            _task = socketTaskBuilder.Register();
  • Chama EnableTransferOwnership no socket antes de associares o 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");

Assim que o seu socket estiver devidamente configurado, quando a sua aplicação estiver prestes a ser suspensa, chame TransferOwnership no socket para o transferir para um intermediário de sockets. O intermediário monitoriza o socket e ativa a sua tarefa em segundo plano quando são recebidos dados. O exemplo seguinte inclui uma função utilitária TransferOwnership para efetuar a transferência de sockets StreamSocketListener. (Note que os diferentes tipos de sockets têm, cada um, o seu próprio método TransferOwnership, pelo que deve chamar o método adequado ao socket cuja propriedade está a transferir. O seu código provavelmente incluiria uma função auxiliar TransferOwnership sobrecarregada, com uma implementação para cada tipo de socket que utiliza, para que o código OnSuspending continue a ser fácil de ler.)

Uma aplicação transfere o controlo de um socket para um mediador de sockets e transmite o ID da tarefa em segundo plano, utilizando o método adequado de entre os seguintes:


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

No processador de eventos da sua tarefa em segundo plano:

  • Primeiro, obtenha um adiamento de tarefas em segundo plano para que possa gerir o evento usando métodos assíncronos.
var deferral = taskInstance.GetDeferral();
  • De seguida, extrai o SocketActivityTriggerDetails dos argumentos do evento e encontra a razão pela qual o evento foi levantado:
var details = taskInstance.TriggerDetails as SocketActivityTriggerDetails;
    var socketInformation = details.SocketInformation;
    switch (details.Reason)
  • Se o evento foi ativado devido à atividade do socket, crie um DataReader no socket, carregue o leitor de forma assíncrona e use os dados de acordo com o design da sua aplicação. Note que deve devolver a propriedade do soquete ao corretor de soquetes, para ser notificado novamente de nova atividade do soquete.

No exemplo seguinte, o texto recebido no socket é exibido num brinde.

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 o evento foi gerado porque expirou um temporizador keep-alive, então o código deve enviar dados através do socket para manter o socket ativo e reiniciar o temporizador keep-alive. Novamente, é importante devolver a posse do socket ao gestor de sockets para receber mais notificações de eventos:
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 o evento foi despoletado porque o socket foi fechado, restabelece o socket, assegurando-te de que, depois de criares o novo socket, transferes a sua posse para o mediador de sockets. Neste exemplo, o nome do host e a porta são armazenados em definições locais para que possam ser usados para estabelecer uma nova ligação ao 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;
  • Não se esqueça de completar o seu adiamento, assim que terminar de processar a notificação do evento:
  deferral.Complete();

Para um exemplo completo que demonstra o uso do SocketActivityTrigger e do broker de sockets, veja o exemplo SocketActivityStreamSocket. A inicialização do socket é feita em Scenario1_Connect.xaml.cs, e a implementação da tarefa em segundo plano está em SocketActivityTask.cs.

Provavelmente notará que o exemplo chama TransferOwnership assim que cria um novo socket ou adquire um socket existente, em vez de usar o handler OnSuspending even para o fazer, como descrito neste tópico. Isto porque o exemplo foca-se em demonstrar o SocketActivityTrigger e não usa o socket para qualquer outra atividade enquanto está a correr. A sua aplicação será provavelmente mais complexa e deverá usar OnSuspending para determinar quando chamar TransferOwnership.

Acionadores do canal de controlo

Primeiro, certifique-se de que está a usar corretamente os gatilhos do canal de controlo (CCTs). Se estiver a usar ligações DatagramSocket, StreamSocket ou StreamSocketListener , então recomendamos que utilize o SocketActivityTrigger. Podes usar CCTs para o StreamSocket, mas eles consomem mais recursos e podem não funcionar no modo Connected Standby.

Se estiver a usar WebSockets, IXMLHTTPRequest2, System.Net.Http.Http.HttpClient ou Windows. Web.Http.HttpClient, então tens de usar ControlChannelTrigger.

ControlChannelTrigger com WebSockets

Importante

A funcionalidade descrita nesta secção (ControlChannelTrigger com WebSockets) é suportada no SDK do Windows versão 10.0.15063.0 e posteriores.

Algumas considerações especiais aplicam-se ao usar MessageWebSocket ou StreamWebSocket com ControlChannelTrigger. Existem alguns padrões de utilização e boas práticas específicas de transporte que devem ser seguidas ao usar um MessageWebSocket ou StreamWebSocket com ControlChannelTrigger. Além disso, estas considerações afetam a forma como os pedidos de receção de pacotes no StreamWebSocket são tratados. Os pedidos para receber pacotes no MessageWebSocket não são afetados.

Os seguintes padrões de utilização e boas práticas devem ser seguidos ao utilizar MessageWebSocket ou StreamWebSocket com ControlChannelTrigger:

  • Um recebimento de socket pendente deve ser mantido informado em todos os momentos. Isto é necessário para permitir que as tarefas de notificação push ocorram.
  • O protocolo WebSocket define um modelo padrão para mensagens keep-alive. A classe WebSocketKeepAlive pode enviar mensagens de manutenção do protocolo WebSocket iniciadas pelo cliente para o servidor. A classe WebSocketKeepAlive deve ser registada como TaskEntryPoint para um KeepAliveTrigger pela aplicação.

Algumas considerações especiais afetam a forma como os pedidos de receção de pacotes no StreamWebSocket são tratados. Em particular, ao usar um StreamWebSocket com o ControlChannelTrigger, a sua aplicação deve usar um padrão assíncrono bruto para tratar as leituras, em vez do modelo await em C# e VB.NET ou Tasks em C++. O padrão assíncrono bruto é ilustrado num exemplo de código mais adiante nesta secção.

A utilização do padrão assíncrono bruto permite ao Windows sincronizar o método IBackgroundTask.Run na tarefa em segundo plano para o ControlChannelTrigger com o retorno do callback de conclusão de receção. O método Run é invocado após o retorno do callback de completão. Isto garante que a aplicação recebeu os dados/erros antes de o método Run ser invocado.

É importante notar que a aplicação tem de publicar outra leitura antes de devolver o controlo do callback de completação. É também importante notar que o DataReader não pode ser usado diretamente com o transporte MessageWebSocket ou StreamWebSocket , pois isso quebra a sincronização descrita acima. Não é suportado usar o método DataReader.LoadAsync diretamente por cima do transporte. Em vez disso, o IBuffer devolvido pelo método IInputStream.ReadAsync na propriedade StreamWebSocket.InputStream pode ser posteriormente passado para o método DataReader.FromBuffer para processamento posterior.

O exemplo seguinte mostra como usar um padrão assíncrono bruto para lidar com leituras no 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);
   }
}

É garantido que o processador de conclusão de leitura seja executado antes de o método IBackgroundTask.Run da tarefa em segundo plano do ControlChannelTrigger ser invocado. O Windows tem sincronização interna para esperar que uma aplicação retorne do callback de conclusão de leitura. A aplicação normalmente processa rapidamente os dados ou o erro provenientes de MessageWebSocket ou de StreamWebSocket na função de callback de conclusão da leitura. A própria mensagem é processada no contexto do método IBackgroundTask.Run . No exemplo abaixo, este ponto é ilustrado usando uma fila de mensagens na qual o gestor de conclusão de leitura insere a mensagem e a tarefa em segundo plano processa posteriormente.

O exemplo seguinte mostra o handler de conclusão de leitura a usar com um padrão assíncrono bruto para lidar com leituras no 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);
}

Um detalhe adicional sobre os WebSockets é o gestor de keep-alive. O protocolo WebSocket define um modelo padrão para mensagens keep-alive.

Ao usar MessageWebSocket ou StreamWebSocket, registe uma instância da classe WebSocketKeepAlive como o TaskEntryPoint de um KeepAliveTrigger, para permitir que a aplicação seja retirada do estado suspenso e envie periodicamente mensagens de manutenção da ligação para o servidor (extremidade remota). Isto deve ser feito como parte do código da aplicação de registo em segundo plano, bem como no manifesto do pacote.

Este ponto de entrada de tarefas do Windows. Sockets.WebSocketKeepAlive precisa de ser especificado em dois locais:

  • Ao criar o gatilho KeepAliveTrigger no código-fonte (ver exemplo abaixo).
  • No pacote da aplicação, manifesto para a declaração de tarefa em segundo plano do keepalive.

O exemplo seguinte adiciona uma notificação de gatilho de rede e um gatilho keepalive sob o <elemento Application> num manifesto de 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>

Uma aplicação deve ser extremamente cuidadosa ao usar uma instrução await no contexto de um ControlChannelTrigger e uma operação assíncrona num StreamWebSocket, MessageWebSocket ou StreamSocket. Um objeto Task<bool> pode ser usado para registar um ControlChannelTrigger para notificações push e manter o WebSocket ativo no StreamWebSocket e ligar o transporte. Como parte do registo, o transporte StreamWebSocket é definido como transporte para o ControlChannelTrigger e uma leitura é publicada. O Task.Result irá bloquear o thread atual até que todos os passos da tarefa sejam executados e retornem as instruções no corpo da mensagem. A tarefa não é resolvida até que o método devolva verdadeiro ou falso. Isto garante que todo o método é executado. A Tarefa pode conter múltiplas instruções await que são protegidas pela Tarefa. Este padrão deve ser usado com o objeto ControlChannelTrigger quando um StreamWebSocket ou MessageWebSocket é usado como transporte. Para operações que podem demorar um longo período de tempo a ser concluídas (uma operação típica de leitura assíncrona, por exemplo), a aplicação deve usar o padrão assíncrono bruto discutido anteriormente.

O exemplo seguinte regista ControlChannelTrigger para notificações push e keep-alives de WebSocket em 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;
}

Para mais informações sobre a utilização do MessageWebSocket ou StreamWebSocket com ControlChannelTrigger, consulte o exemplo do ControlChannelTrigger StreamWebSocket.

ControlChannelTrigger com HttpClient

Algumas considerações especiais aplicam-se ao usar HttpClient com ControlChannelTrigger. Existem alguns padrões de utilização e boas práticas específicas de transporte que devem ser seguidas ao usar um Cliente Http com ControlChannelTrigger. Além disso, estas considerações afetam a forma como os pedidos de receção de pacotes no HttpClient são tratados.

NotaO HttpClient com SSL não é suportado atualmente com a funcionalidade de acionamento de rede e com o ControlChannelTrigger.   Devem ser seguidos os seguintes padrões de utilização e melhores práticas ao utilizar HttpClient com ControlChannelTrigger:

  • A aplicação pode precisar de definir várias propriedades e cabeçalhos no objeto HttpClient ou HttpClientHandler no espaço de nomes System.Net.Http antes de enviar o pedido para o URI específico.
  • Uma aplicação pode precisar de efetuar um pedido inicial para testar e configurar corretamente o transporte antes de criar o transporte HttpClient a utilizar com ControlChannelTrigger. Uma vez que a aplicação determina que o transporte pode ser configurado corretamente, um objeto HttpClient pode ser configurado como o objeto de transporte usado com o objeto ControlChannelTrigger . Este processo foi concebido para evitar que alguns cenários rompam a ligação estabelecida sobre o transporte. Usando SSL com certificado, uma aplicação pode exigir que seja exibido um diálogo para introdução do PIN ou se houver vários certificados para escolher. Podem ser necessárias a autenticação por proxy e a autenticação do servidor. Se o proxy ou autenticação do servidor expirar, a ligação pode ser encerrada. Uma forma de uma aplicação lidar com estes problemas de expiração da autenticação é definir um temporizador. Quando é necessário um redirecionamento HTTP, não é garantido que a segunda ligação possa ser estabelecida de forma fiável. Um pedido de teste inicial garantirá que a aplicação pode utilizar o URL redirecionado mais atual antes de usar o objeto HttpClient como transporte com o objeto ControlChannelTrigger.

Ao contrário de outros transportes de rede, o objeto HttpClient não pode ser passado diretamente para o método UsingTransport do objeto ControlChannelTrigger . Em vez disso, um objeto HttpRequestMessage deve ser especialmente construído para uso com o objeto HttpClient e o ControlChannelTrigger. O objeto HttpRequestMessage é criado usando o método RtcRequestFactory.Create . O objeto HttpRequestMessage que é criado é então passado para o método UsingTransport .

O exemplo seguinte mostra como construir um objeto HttpRequestMessage para usar com o objeto HttpClient e o 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;
    }
}

Algumas considerações especiais afetam a forma como os pedidos para enviar pedidos HTTP no HttpClient para iniciar a receção de uma resposta são tratados. Em particular, ao usar um Cliente Http com o ControlChannelTrigger, a sua aplicação deve usar uma Tarefa para tratar envios em vez do modelo await .

Usando HttpClient, não há sincronização com o método IBackgroundTask.Run na tarefa em segundo plano do ControlChannelTrigger aquando do retorno da chamada de retorno de conclusão da receção. Por esta razão, a aplicação só pode usar a técnica de bloqueio HttpResponseMessage no método Run e esperar até que a resposta completa seja recebida.

Usar HttpClient com ControlChannelTrigger é visivelmente diferente dos transportes StreamSocket, MessageWebSocket e StreamWebSocket. O callback de receção do HttpClient é entregue à aplicação através de uma Task pelo código do HttpClient. Isto significa que a tarefa de notificação push do ControlChannelTrigger será ativada assim que os dados ou erros forem enviados para a aplicação. No exemplo abaixo, o código armazena a responseTask devolvida pelo método HttpClient.SendAsync num armazenamento global que a tarefa push notify irá recolher e processar em linha.

O exemplo seguinte mostra como lidar com pedidos de envio no HttpClient quando usado com 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);
}

O exemplo seguinte mostra como ler as respostas recebidas no HttpClient quando usado com o 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;
}

Para mais informações sobre a utilização do HttpClient com o ControlChannelTrigger, consulte o exemplo do HttpClient do ControlChannelTrigger.

ControlChannelTrigger com IXMLHttpRequest2

Algumas considerações especiais aplicam-se ao usar IXMLHTTPRequest2 com ControlChannelTrigger. Existem alguns padrões de utilização específicos de transporte e boas práticas que devem ser seguidas ao usar um IXMLHTTPRequest2 com ControlChannelTrigger. O uso do ControlChannelTrigger não afeta a forma como os pedidos para enviar ou receber pedidos HTTP no IXMLHTTPRequest2 são tratados.

Padrões de utilização e melhores práticas ao utilizar o IXMLHTTPRequest2 com ControlChannelTrigger

  • Um objeto IXMLHTTPRequest2 , quando usado como transporte, tem uma vida útil de apenas um pedido/resposta. Quando usado com o objeto ControlChannelTrigger , é conveniente criar e configurar o objeto ControlChannelTrigger uma vez e depois chamar repetidamente o método UsingTransport , associando cada vez um novo objeto IXMLHTTPRequest2 . Uma aplicação deve eliminar o objeto IXMLHTTPRequest2 anterior antes de fornecer um novo objeto IXMLHTTPRequest2 para garantir que a aplicação não ultrapassa os limites de recursos alocados.
  • A aplicação pode precisar de chamar os métodos SetProperty e SetRequestHeader para configurar o transporte HTTP antes de chamar o método Send .
  • Uma aplicação pode ter de fazer um pedido Send inicial para testar e configurar corretamente o transporte antes de criar o transporte que será utilizado com ControlChannelTrigger. Uma vez que a aplicação determina que o transporte está devidamente configurado, o objeto IXMLHTTPRequest2 pode ser configurado como o objeto de transporte usado com o ControlChannelTrigger. Este processo foi concebido para evitar que alguns cenários rompam a ligação estabelecida sobre o transporte. Usando SSL com certificado, uma aplicação pode exigir que seja exibido um diálogo para introdução do PIN ou se houver vários certificados para escolher. Podem ser necessárias a autenticação por proxy e a autenticação do servidor. Se o proxy ou autenticação do servidor expirar, a ligação pode ser encerrada. Uma forma de uma aplicação lidar com estes problemas de expiração da autenticação é definir um temporizador. Quando é necessário um redirecionamento HTTP, não é garantido que a segunda ligação possa ser estabelecida de forma fiável. Um pedido de teste inicial garantirá que a aplicação pode utilizar o URL redirecionado mais atualizado antes de usar o objeto IXMLHTTPRequest2 como transporte com o objeto ControlChannelTrigger.

Para mais informações sobre o uso do IXMLHTTPRequest2 com ControlChannelTrigger, consulte o exemplo do ControlChannelTrigger com o IXMLHTTPRequest2.

APIs importantes