Expondo tarefas .NET como operações assíncronas do WinRT

Blog do desenvolvedor de aplicativos do Windows 8

Ideias sobre a criação de aplicativos com o estilo Metro para o Windows 8, da equipe de engenharia do Windows 8

Expondo tarefas .NET como operações assíncronas do WinRT

  • Comments 0

A postagem recente do blog Compreendendo melhor WinRT e await, discutimos as palavras-chave async e await no C# e no Visual Basic e como você pode empregá-las para usar operações assíncronas do WinRT (Tempo de Execução do Windows).

Com alguma ajuda de BCL (Bibliotecas de Classe Base) do .NET, você também pode usar essas palavras-chave para desenvolver operações assíncronas que depois serão expostas pelo WinRT para serem usadas por outros componentes criados em linguagens diferentes. Nesta postagem, veremos como fazer isso. Para obter os detalhes gerais sobre a implementação de componentes do WinRT com C# ou Visual Basic, consulte Criando componentes do Tempo de execução do Windows em C# e Visual Basic.

Vamos começar examinando o formato das APIs assíncronas no WinRT.

Interfaces assíncronas do WinRT

O WinRT possui várias interfaces relacionadas a operações assíncronas. A primeira é a IAsyncInfo, que toda operação assíncrona válida do WinRT implementa. Ela revela os recursos comuns para uma operação assíncrona, inclusive o status atual da operação, o erro da operação quando há falha uma ID para a operação e sua capacidade de permitir a solicitação de cancelamento:

public interface IAsyncInfo
{
AsyncStatus Status { get; }
Exception ErrorCode { get; }
uint Id { get; }

void Cancel();
void Close();
}

Além de implementar IAsyncInfo, toda operação assíncrona válida do WinRT implementa uma das quatro interfaces adicionais: IAsyncAction, IAsyncActionWithProgress<TProgress>, IAsyncOperation<TResult> ou IAsyncOperationWithProgress<TResult,TProgress>. Essas interfaces adicionais permitem ao consumidor definir um retorno de chamada que seja invocado quando o trabalho assíncrono é concluído e, opcionalmente, permite que o consumidor obtenha um resultado e/ou receba relatórios de progresso:

// No results, no progress
public interface IAsyncAction : IAsyncInfo
{
AsyncActionCompletedHandler Completed { get; set; }
void GetResults();
}

// No results, with progress
public interface IAsyncActionWithProgress<TProgress> : IAsyncInfo
{
AsyncActionWithProgressCompletedHandler<TProgress> Completed { get; set; }
AsyncActionProgressHandler<TProgress> Progress { get; set; }
void GetResults();
}

// With results, no progress
public interface IAsyncOperation<TResult> : IAsyncInfo
{
AsyncOperationCompletedHandler<TResult> Completed { get; set; }
TResult GetResults();
}

// With results, with progress
public interface IAsyncOperationWithProgress<TResult,TProgress> : IAsyncInfo
{
AsyncOperationWithProgressCompletedHandler<TResult,TProgress> Completed { get; set; }
AsyncOperationProgressHandler<TResult,TProgress> Progress { get; set; }
TResult GetResults();
}

IAsyncAction fornece um método GetResults mesmo embora não haja resultados a serem retornados. Isso fornece um método em uma operação assíncrona com falha para lançar uma exceção, em vez de forçar todos os consumidores a acessar a propriedade ErrorCode de IAsyncInfo. É semelhante à forma como os tipos de awaiter em C# e Visual Basic expõem um método GetResult, mesmo que o método GetResult seja tipado para retornar void.

Ao criar uma biblioteca WinRT, todas as operações assíncronas expostas publicamente nela são fortemente tipadas para retornar uma dessas quatro interfaces. Entretanto, as novas operações assíncronas expostas pelas bibliotecas .NET seguem o padrão TAP (Padrão assíncrono baseado em tarefas), retornando Task ou Task<TResult>, o primeiro para operações que não retornam um resultado e o último para operações que retornam.

Task e Task<TResult> não implementam essas interfaces do WinRT nem o CLR (Common Language Runtime) implicitamente documentar as diferenças (como faz para alguns tipos, como o tipo do WinRT Windows.Foundation.Uri e do BCL System.Uri). Em vez disso, precisamos converter explicitamente de um universo para outro. Na postagem Compreendendo melhor WinRT e await, vimos como o método de extensão AsTask no BCL fornece um mecanismo para converter interfaces assíncronas do WinRT em Tasks do .NET. O BCL também dá suporte no outro sentido, com métodos para converter Tasks do .NET em interfaces assíncronas do WinRT.

Convertendo com AsAsyncAction e AsAsyncOperation

Para a finalidade desta postagem do blog, suponhamos que tenho um método assíncrono .NET DownloadStringAsyncInternal. Passamos para ele uma URL de uma página da Web, e o método assíncrono baixa e retorna o conteúdo dessa página como uma cadeia de caracteres:

internal static Task<string> DownloadStringAsyncInternal(string url);

A forma como esse método é implementado não importa. Nosso objetivo é encapsular isso como uma operação assíncrona do WinRT, ou seja, um método que retorne uma das quatro interfaces mencionadas anteriormente. Como nossa operação tem um resultado (uma cadeia de caracteres) e não dá suporte ao relatório do progresso, nossa operação assíncrona do WinRT retorna IAsyncOperation<string>:

public static IAsyncOperation<string> DownloadStringAsync(string url);

Para implementar esse método, podemos chamar o método DownloadStringAsyncInternal para obter a Task<string> resultante. Depois, precisamos converter essa tarefa na IAsyncOperation<string> necessária, mas como?

public static IAsyncOperation<string> DownloadStringAsync(string url)
{
Task<string> from = DownloadStringAsyncInternal(url);
IAsyncOperation<string> to = ...; // TODO: how do we convert 'from'?
return to;
}

Para resolver essa lacuna, o assembly System.Runtime.WindowsRuntime.dll no .NET 4.5 inclui métodos de extensão para Task e Task<TResult> que fornecem as conversões necessárias:

// in System.Runtime.WindowsRuntime.dll
public static class WindowsRuntimeSystemExtensions
{
public static IAsyncAction AsAsyncAction(
this Task source);
public static IAsyncOperation<TResult> AsAsyncOperation<TResult>(
this Task<TResult> source);
...
}

Esses métodos retornam uma nova instância IAsyncAction ou IAsyncOperation<TResult> que encapsula a Task ou Task<TResult> fornecida, respectivamente (como Task<TResult> é derivada de Task, os dois métodos estão disponíveis para Task<TResult>, embora seja relativamente raro o uso de AsAsyncAction com um método assíncrono de retorno de resultado). Pela lógica, você pode considerar essas operações explícitas, conversões síncronas ou de uma perspectiva de padrão de design, como adaptadores. Elas retornam uma instância que representa a tarefa subjacente, mas expõe a área de superfície necessária para o WinRT. Com tais métodos de extensão, podemos completar a implementação de DownloadStringAsync:

public static IAsyncOperation<string> DownloadStringAsync(string url)
{
Task<string> from = DownloadStringAsyncInternal(url);
IAsyncOperation<string> to =
from.AsAsyncOperation();
return to;
}

Também podemos escrever isso de forma mais sucinta, destacando como é a operação do tipo conversão:

public static IAsyncOperation<string> DownloadStringAsync(string url)
{
return DownloadStringAsyncInternal(url)
.AsAsyncOperation();
}

DownloadStringAsyncInternal está sendo invocado antes de chamar AsAsyncOperation. Isso significa que precisamos que a chamada síncrona a DownloadStringAsyncInternal seja retornada rapidamente para garantir que o método wrapper DownloadStringAsync tenha grande capacidade de resposta. Se, por algum motivo, você recear que o trabalho síncrono que está fazendo demore muito ou se quiser explicitamente descarregar a execução em um pool de threads por outros motivos, poderá fazê-lo usando Task.Run e invocando AsAsyncOperation na tarefa retornada:

public static IAsyncOperation<string> DownloadStringAsync(string url)
{
return Task.Run(()=>DownloadStringAsyncInternal(url)).AsAsyncOperation();
}

Mais flexibilidade com AsyncInfo.Run

Os métodos de extensão internos AsAsyncAction e AsAsyncOperation são ótimos para conversões simples de Task para IAsyncAction e de Task<TResult> para IAsyncOperation<TResult>. Mas e as conversões mais avançadas?

System.Runtime.WindowsRuntime.dll contém outro tipo que fornece mais flexibilidade: AsyncInfo, no namespace System.Runtime.InteropServices.WindowsRuntime. AsyncInfo expõe quatro sobrecargas de um método Run estático, um para cada uma das quatro interfaces assíncronas do WinRT:

// in System.Runtime.WindowsRuntime.dll
public static class AsyncInfo
{
// No results, no progress
public static IAsyncAction Run(
Func<CancellationToken,
Task> taskProvider);

// No results, with progress
public static IAsyncActionWithProgress<TProgress> Run<TProgress>(
Func<CancellationToken,
IProgress<TProgress>,
Task> taskProvider);


// With results, no progress
public static IAsyncOperation<TResult> Run<TResult>(
Func<CancellationToken,
Task<TResult>> taskProvider);

// With results, with progress
public static IAsyncOperationWithProgress<TResult, TProgress> Run<TResult, TProgress>(
Func<CancellationToken,
IProgress<TProgress>,
Task<TResult>> taskProvider);
}

Os métodos AsAsyncAction e AsAsyncOperation que já examinamos aceitam uma Task como um argumento. No entanto, esses métodos Run aceitam um delegado de função que retorna uma Task, e a diferença entre Task e Func<…,Task> é o suficiente para nos dar a flexibilidade extra de que precisamos para operações mais avançadas.

Obviamente, você pode achar que AsAsyncAction e AsAsyncOperation são simples auxiliares do AsyncInfo.Run mais avançado:

public static IAsyncAction AsAsyncAction(
this Task source)
{
return AsyncInfo.Run(_ => source);
}

public static IAsyncOperation<TResult> AsAsyncOperation<TResult>(
this Task<TResult> source)
{
return AsyncInfo.Run(_ => source);
}

Não é exatamente desse jeito que eles são implementados no .NET 4.5, mas na prática eles se comportam dessa forma, por isso convém pensar assim para comparar o suporte básico e avançado. Se você tiver um caso simples, use AsAsyncAction e AsAsyncOperation, mas há vários casos avançados em que AsyncInfo.Run se sobressai.

Cancelamento

AsyncInfo.Run torna possível dar suporte ao cancelamento com métodos assíncronos do WinRT.

Continuando com o nosso exemplo de download, digamos que haja outra sobrecarga de DownloadStringAsyncInternal que aceite um CancellationToken:

internal static Task<string> DownloadStringAsyncInternal(
string url, CancellationToken cancellationToken);

CancellationToken é um tipo do .NET Framework que dá suporte a cancelamento cooperativo de uma forma combinável. Você pode passar um único token para um número indefinido de chamadas de método e, quando for solicitado seu cancelamento (com o CancellationTokenSource que criou o token), a solicitação de cancelamento ficará visível para todas as operações usadas. Essa abordagem difere ligeiramente da usada pela assincronia do WinRT, que faz com que cada IAsyncInfo exponha seu próprio método Cancel. Dito isso, como fazemos uma chamada de Cancel em IAsyncOperation<string> gerar uma solicitação de cancelamento em um CancellationToken que seja passa para DownloadStringAsyncInternal? AsAsyncOperation não funcionará nesse caso:

public static IAsyncOperation<string> DownloadStringAsync(string uri)
{
return DownloadStringAsyncInternal(uri, … /* what goes here?? */)
.AsAsyncOperation();
}

Para saber quando o cancelamento é solicitado da IAsyncOperation<string>, essa instância precisaria notificar de alguma forma que seu método Cancel foi chamado, por exemplo, solicitando o cancelamento de um CancellationToken que passaríamos para DownloadStringAsyncInternal. Mas, em um "impasse" clássico, só obtemos a Task<string> para invocar AsAsyncOperation depois que invocamos DownloadStringAsyncInternal, mas antes teríamos de fornecer o próprio CancellationToken que queríamos que AsAsyncOperation fornecesse.

Há várias maneiras de resolver essa charada, inclusive a solução empregada por AsyncInfo.Run. O método Run é responsável por construir a IAsyncOperation<string> e cria essa instância para solicitar o cancelamento de um CancellationToken que também cria quando o método Cancel de operações assíncronas é chamado. Depois, ao invocar o delegado fornecido pelo usuário e passado para Run, ele passa esse token, evitando o ciclo discutido anteriormente:

public static IAsyncOperation<string> DownloadStringAsync(string uri)
{
return AsyncInfo.Run(cancellationToken =>
DownloadStringAsyncInternal(uri, cancellationToken));
}

Lambdas e métodos anônimos

AsyncInfo.Run simplifica o uso de funções lambda e métodos anônimos para implementar métodos assíncronos WinRT.

Por exemplo, se já não tivéssemos o método DownloadStringAsyncInternal, poderíamos implementá-lo, da mesma forma que DownloadStringAsync, desta maneira:

public static IAsyncOperation<string> DownloadStringAsync(string uri)
{
return AsyncInfo.Run(delegate(CancellationToken cancellationToken)
{
return DownloadStringAsyncInternal(uri, cancellationToken));
});
}

private static async Task<string> DownloadStringAsyncInternal(
string uri, CancellationToken cancellationToken)
{
var response = await new HttpClient().GetAsync(
uri, cancellationToken);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}

Aproveitando o suporte do C# e Visual Basic para escrever métodos anônimos assíncronos, podemos simplificar nossa implementação, combinando esses dois métodos em apenas um:

public static IAsyncOperation<string> DownloadStringAsync(string uri)
{
return AsyncInfo.Run(
async delegate(CancellationToken cancellationToken)
{
var response = await new HttpClient().GetAsync(
uri, cancellationToken);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();

});
}

Progresso

AsyncInfo.Run também dá suporte ao relatório de progresso com métodos assíncronos do WinRT.

Em vez de o nosso método DownloadStringAsync retornar uma IAsyncOperation<string>, imagine que quiséssemos que retornasse uma IAsyncOperationWithProgress<string,int>:

public static IAsyncOperationWithProgress<string,int> DownloadStringAsync(string uri);

DownloadStringAsync agora pode fornecer atualizações de progresso contendo dados integrais, de forma que os consumidores podem definir um delegado com a propriedade Progress da interface para receber notificações da alteração de progresso.

AsyncInfo.Run fornece uma sobrecarga que aceita Func<CancellationToken,IProgress<TProgress>,Task<TResult>>. Da mesma forma que AsyncInfo.Run passa para o delegado um CancellationToken que gerará a solicitação de cancelamento quando o método Cancel for chamado, ele também pode passar uma instância IProgress<TProgress> cujo método Report dispara invocações do delegado Progress do consumidor. Por exemplo, se quiséssemos modificar nosso exemplo anterior para informar 0% de progresso no início, 50% de progresso depois de obter a resposta e 100% de progresso depois de analisar a resposta em uma cadeia de caracteres, ele ficaria da seguinte forma:

public static IAsyncOperationWithProgress<string,int> DownloadStringAsync(string uri)
{
return AsyncInfo.Run(async delegate(
CancellationToken cancellationToken, IProgress<int> progress)
{
progress.Report(0);
try
{
var response = await new HttpClient().GetAsync(uri, cancellationToken);
progress.Report(50);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
finally
{ progress.Report(100); }
});
}

Por trás dos bastidores

Para obter uma boa imagem mental de como isso funciona, vejamos a implementação de AsAsyncOperation e AsyncInfo.Run. Não se tratam das mesmas implementações que existem no .NET 4.5 e nem são tão robustas. Na verdade, são aproximações que fornecem uma boa ideia de como as coisas funcionam nos bastidores e não devem ser usadas em produção.

AsAsyncOperation

O método AsAsyncOperation usa Task<TResult> e retorna IAsyncOperation<TResult>:

public static IAsyncOperation<TResult> AsAsyncOperation<TResult>(
this Task<TResult> source);

Para implementar esse método, precisamos criar um tipo que implemente IAsyncOperation<TResult> e isso encapsule a tarefa fornecida.

public static IAsyncOperation<TResult> AsAsyncOperation<TResult>(
this Task<TResult> source)
{
return new TaskAsAsyncOperationAdapter<TResult>(source);
}

internal class TaskAsAsyncOperationAdapter<TResult> : IAsyncOperation<TResult>
{
private readonly Task<TResult> m_task;

public TaskAsAsyncOperationAdapter(Task<TResult> task) { m_task = task; }

...
}

Cada implementação do método da interface nesse tipo será delegada à funcionalidade na tarefa encapsulada. Vamos começar com os membros mais fáceis.

Primeiro, o método IAsyncInfo.Close deve otimizar intensamente qualquer recurso que a operação assíncrona concluída usou. Como não temos tais recursos (nosso objeto apenas encapsula uma tarefa), nossa implementação é vazia:

public void Close() { /* NOP */ }

Com o método IAsyncInfo.Cancel, um consumidor da operação assíncrona solicita seu cancelamento. É apenas uma solicitação e não força a saída da operação de forma alguma. Simplesmente, usamos um CancellationTokenSource para armazenar que uma solicitação ocorreu:

private readonly CancellationTokenSource m_canceler =
new CancellationTokenSource();

public void Cancel() { m_canceler.Cancel(); }

A propriedade IAsyncInfo.Status retorna um AsyncStatus para representar o status atual da operação em relação a seu ciclo de vida assíncrono. Este pode ser um dos quatro valores: Started, Completed, Error ou Canceled. Em geral, podemos simplesmente delegar para a propriedade subjacente Status de Task e passar o TaskStatus retornado para o AsyncStatus necessário:

De TaskStatus

Para AsyncStatus

RanToCompletion

Concluído

Falha

Erro

Cancelado

Cancelado

Todos os outros valores e cancelamento foram solicitados

Cancelado

Todos os outros valores e cancelamento não foram solicitados

Iniciado

Se a Task ainda não foi concluída e o cancelamento foi solicitado, precisamos retornar Canceled em vez de Started. Isso significa que, embora TaskStatus.Canceled seja apenas um estado terminal, AsyncStatus.Canceled pode ser um estado terminal ou não terminal, já que é possível para uma IAsyncInfo terminar no estado Canceled ou para uma IAsyncInfo no estado Canceled passar para AsyncStatus.Completed ou AsyncStatus.Error.

public AsyncStatus Status
{
get
{
switch (m_task.Status)
{
case TaskStatus.RanToCompletion:
return AsyncStatus.Completed;
case TaskStatus.Faulted:
return AsyncStatus.Error;
case TaskStatus.Canceled:
return AsyncStatus.Canceled;
default:
return m_canceler.IsCancellationRequested ?
AsyncStatus.Canceled :
AsyncStatus.Started;
}
}
}

A propriedade IAsyncInfo.Id retorna um identificador UInt32 para a operação. Como Task em si já expõe tal identificador (como um Int32), podemos implementar essa propriedade apenas delegando com a propriedade Task subjacente:

public uint Id { get { return (uint)m_task.Id; } }

O WinRT define a propriedade IAsyncInfo.ErrorCode para que retorne um HResult. Mas o CLR internamente passa o HResult do WinRT para uma Exception do .NET, expondo-o dessa forma para nós com a projeção gerenciada. Task em si expõe uma propriedade Exception, então podemos apenas delegar para ela: se Task terminar no estado Faulted, retornaremos sua primeira exceção (uma Task pode falhar devido a várias exceções, como uma Task retornada de Task.WhenAll), retornando null caso contrário:

public Exception ErrorCode
{
get { return m_task.IsFaulted ? m_task.Exception.InnerException : null; }
}

Essa é a implementação de IAsyncInfo. Agora precisamos implementar os dois membros adicionais fornecidos por IAsyncOperation<TResult>: GetResults e Completed.

Os consumidores chamam GetResults depois que a operação é concluída com êxito ou depois que uma exceção ocorre. No primeiro caso, ele retorna o resultado calculado e, no último, ele lança a exceção relevante. Se a operação terminou como Canceled ou se não terminou ainda, é ilegal invocar GetResults. Assim, podemos implementar GetResults da seguinte maneira, contando com o método GetResult do awaiter da tarefa para retornar o resultado se bem-sucedido ou propagar a exceção adequada se a tarefa terminou como Faulted.

public TResult GetResults()
{
switch (m_task.Status)
{
case TaskStatus.RanToCompletion:
case TaskStatus.Faulted:
return m_task.GetAwaiter().GetResult();
default:
throw new InvalidOperationException("Invalid GetResults call.");
}
}

Finalmente, temos a propriedade Completed. Completed representa um delegado a ser invocado quando a operação é concluída. Se a operação já estiver concluída quando Completed for definido, o delegado fornecido deve ser invocado ou programado imediatamente. Além disso, um consumidor pode definir a propriedade apenas uma vez (tentativas de definir várias vezes geram exceções). E, depois que a operação foi concluída, as implementações devem deixar a referência para o delegado a fim de evitar perdas de memória. Podemos contar com o método ContinueWith's Task para implementar boa parte desse comportamento, mas como ContinueWith pode ser usado muitas vezes na mesma instância de Task, precisamos implementar manualmente o comportamento "set once" mais restritivo:

private AsyncOperationCompletedHandler<TResult> m_handler;
private int m_handlerSet;

public AsyncOperationCompletedHandler<TResult> Completed
{
get { return m_handler; }
set
{
if (value == null)
throw new ArgumentNullException("value");
if (Interlocked.CompareExchange(ref m_handlerSet, 1, 0) != 0)
throw new InvalidOperationException("Handler already set.");

m_handler = value;

var sc = SynchronizationContext.Current;
m_task.ContinueWith(delegate {
var handler = m_handler;
m_handler = null;
if (sc == null)
handler(this, this.Status);
else
sc.Post(delegate { handler(this, this.Status); }, null);
}, CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
}
}

Com isso, nossa implementação de AsAsyncOperation está concluída e podemos usar qualquer método de retorno Task<TResult> como um método IAsyncOperation<TResult>.

AsyncInfo.Run

Agora, que tal mais um pouco de AsyncInfo.Run avançado?

public static IAsyncOperation<TResult> Run<TResult>(
Func<CancellationToken,Task<TResult>> taskProvider);

Para dar suporte à sobrecarga de Run, podemos usar o mesmo tipo TaskAsAsyncOperationAdapter<TResult> que acabamos de criar, com toda a implementação existente intacta. Na verdade, precisamos apenas ampliar a capacidade de trabalhar em termos de Func<CancellationToken,Task<TResult>>, em vez de apenas Task<TResult>. Quando esse tipo de delegado é fornecido, podemos simplesmente invocá-lo de forma síncrona, passando o m_canceler CancellationTokenSource definido anteriormente e armazenando a tarefa retornada:

internal class TaskAsAsyncOperationAdapter<TResult> : IAsyncOperation<TResult>
{
private readonly Task<TResult> m_task;

public TaskAsAsyncOperationAdapter(Task<TResult> task) { m_task = task; }

public TaskAsAsyncOperationAdapter(
Func<CancellationToken,Task<TResult>> func)
{
m_task = func(m_canceler.Token);
}
...
}

public static class AsyncInfo
{
public static IAsyncOperation<TResult> Run<TResult>(
Func<CancellationToken, Task<TResult>> taskProvider)
{
return new TaskAsAsyncOperationAdapter<TResult>(taskProvider);
}

}

Essa implementação destaca que não existe nenhuma mágica nisso. AsAsyncAction, AsAsyncOperation e AsyncInfo.Run são apenas implementações auxiliares que o poupam de escrever todo esse código clichê sozinho.

Conclusão

Agora, espero que você tenha compreendido melhor como AsAsyncAction, AsAsyncOperation e AsyncInfo.Run podem ajudá-lo: elas tornam mais fácil usar Task ou Task<TResult> e as expor como IAsyncAction, IAsyncOperation<TResult>, IAsyncActionWithProgress<TProgress> ou IAsyncOperationWithProgress<TResult,TProgress>. Combinado às palavras-chave async e await em C# e Visual Basic, fica muito mais fácil implementar novas operações assíncronas do WinRT em código gerenciado.

Contanto que a funcionalidade que você está expondo esteja disponível como Task ou Task<TResult>, conte com esses recursos internos para fazer as conversões para você, em vez de implementar as interfaces assíncronas do WInRT manualmente. E se a funcionalidade que você está tentando expor não estiver disponível como Task ou Task<TResult>, tente expô-la como Task ou Task<TResult> primeiro e depois conte com as conversões internas. Pode ser bastante difícil acertar toda essa semântica em uma implementação de interface assíncrona do WinRT e é por isso que essas conversões existem para fazer esse trabalho por você. Você também terá esse tipo de suporte se estiver implementando operações assíncronas do WinRT em C++, seja com a classe AsyncBase básica da Biblioteca do Tempo de Execução do Windows ou com a função create_async na Biblioteca de Padrões Paralelos.

Boa sorte na assincronia.

--Stephen Toub, Visual Studio

  • Loading...
Leave a Comment
  • Please add 5 and 8 and type the answer here:
  • Post