Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Vous pouvez implémenter le modèle asynchrone basé sur des tâches (TAP) de trois façons : à l’aide des compilateurs C# et Visual Basic dans Visual Studio, manuellement ou via une combinaison du compilateur et des méthodes manuelles. Les sections suivantes décrivent chaque méthode en détail. Vous pouvez utiliser le modèle TAP pour implémenter des opérations asynchrones liées au calcul et liées aux E/S. La section Charges de travail décrit chaque type d’opération.
Génération de méthodes TAP
Utilisation des compilateurs
À compter de .NET Framework 4.5, toute méthode attribuée avec le async
mot clé (Async
en Visual Basic) est considérée comme une méthode asynchrone, et les compilateurs C# et Visual Basic effectuent les transformations nécessaires pour implémenter la méthode de manière asynchrone à l’aide de TAP. Une méthode asynchrone doit retourner un System.Threading.Tasks.Task ou un System.Threading.Tasks.Task<TResult> objet. Pour ce dernier, le corps de la fonction doit retourner un TResult
, et le compilateur garantit que ce résultat est rendu disponible via l’objet de tâche résultant. De même, toutes les exceptions qui ne sont pas gérées dans le corps de la méthode sont marshalées vers la tâche de sortie et provoquent la fin de la tâche résultante dans l’état TaskStatus.Faulted . L’exception à cette règle est lorsqu’un OperationCanceledException (ou type dérivé) n’est pas géré, auquel cas la tâche résultante se termine dans l’état TaskStatus.Canceled .
Génération manuelle de méthodes TAP
Vous pouvez implémenter manuellement le modèle TAP pour mieux contrôler l’implémentation. Le compilateur s'appuie sur la surface publique exposée depuis l'espace de noms System.Threading.Tasks et sur les types de prise en charge de l'espace de noms System.Runtime.CompilerServices. Pour implémenter le TAP vous-même, vous créez un TaskCompletionSource<TResult> objet, effectuez l’opération asynchrone et, quand elle se termine, appelez la SetResultméthode SetException, ou SetCanceled la Try
version de l’une de ces méthodes. Lorsque vous implémentez manuellement une méthode TAP, vous devez effectuer la tâche résultante lorsque l’opération asynchrone représentée se termine. Par exemple:
public static Task<int> ReadTask(this Stream stream, byte[] buffer, int offset, int count, object state)
{
var tcs = new TaskCompletionSource<int>();
stream.BeginRead(buffer, offset, count, ar =>
{
try { tcs.SetResult(stream.EndRead(ar)); }
catch (Exception exc) { tcs.SetException(exc); }
}, state);
return tcs.Task;
}
<Extension()>
Public Function ReadTask(stream As Stream, buffer() As Byte,
offset As Integer, count As Integer,
state As Object) As Task(Of Integer)
Dim tcs As New TaskCompletionSource(Of Integer)()
stream.BeginRead(buffer, offset, count, Sub(ar)
Try
tcs.SetResult(stream.EndRead(ar))
Catch exc As Exception
tcs.SetException(exc)
End Try
End Sub, state)
Return tcs.Task
End Function
Approche hybride
Vous pouvez trouver utile d’implémenter le modèle TAP manuellement, mais de déléguer la logique principale de l’implémentation au compilateur. Par exemple, vous pouvez utiliser l’approche hybride lorsque vous souhaitez vérifier les arguments en dehors d’une méthode asynchrone générée par le compilateur afin que les exceptions puissent s’échapper à l’appelant direct de la méthode plutôt que d’être exposées via l’objet System.Threading.Tasks.Task :
public Task<int> MethodAsync(string input)
{
if (input == null) throw new ArgumentNullException("input");
return MethodAsyncInternal(input);
}
private async Task<int> MethodAsyncInternal(string input)
{
// code that uses await goes here
return value;
}
Public Function MethodAsync(input As String) As Task(Of Integer)
If input Is Nothing Then Throw New ArgumentNullException("input")
Return MethodAsyncInternal(input)
End Function
Private Async Function MethodAsyncInternal(input As String) As Task(Of Integer)
' code that uses await goes here
return value
End Function
Un autre cas où cette délégation est utile est lorsque vous implémentez l’optimisation du chemin rapide et que vous souhaitez retourner une tâche mise en cache.
Charges de travail
Vous pouvez implémenter des opérations asynchrones liées au calcul et liées aux E/S en tant que méthodes TAP. Toutefois, lorsque les méthodes TAP sont exposées publiquement à partir d’une bibliothèque, elles doivent être fournies uniquement pour les charges de travail qui impliquent des opérations liées aux E/S (elles peuvent également impliquer un calcul, mais ne doivent pas être purement informatiques). Si une méthode est purement liée au calcul, elle ne doit être exposée qu’en tant qu’implémentation synchrone. Le code qui l’utilise peut ensuite choisir d’encapsuler un appel de cette méthode synchrone dans une tâche pour décharger le travail vers un autre thread ou obtenir un parallélisme. Et si une méthode est liée aux E/S, elle ne doit être exposée qu’en tant qu’implémentation asynchrone.
Tâches liées au calcul
La System.Threading.Tasks.Task classe est idéale pour représenter des opérations nécessitant beaucoup de ressources de calcul. Par défaut, il tire parti de la prise en charge spéciale au sein de la ThreadPool classe pour fournir une exécution efficace, et fournit également un contrôle significatif sur le moment, l’endroit et la façon dont les calculs asynchrones s’exécutent.
Vous pouvez générer des tâches liées au calcul de la manière suivante :
Dans .NET Framework 4.5 et versions ultérieures (y compris .NET Core et .NET 5+), utilisez la méthode statique Task.Run comme raccourci vers TaskFactory.StartNew. Vous pouvez utiliser Run pour lancer facilement une tâche liée au calcul qui cible le pool de threads. Il s’agit du mécanisme préféré pour lancer une tâche liée au calcul. Utilisez
StartNew
directement uniquement lorsque vous souhaitez un contrôle plus précis sur la tâche.Dans .NET Framework 4, utilisez la TaskFactory.StartNew méthode, qui accepte un délégué (généralement un Action<T> ou un Func<TResult>) à exécuter de manière asynchrone. Si vous fournissez un Action<T> délégué, la méthode retourne un System.Threading.Tasks.Task objet qui représente l’exécution asynchrone de ce délégué. Si vous fournissez un Func<TResult> délégué, la méthode retourne un System.Threading.Tasks.Task<TResult> objet. Les surcharges de la StartNew méthode acceptent un jeton d’annulation (CancellationToken), des options de création de tâche (TaskCreationOptions) et un planificateur de tâches (TaskScheduler), qui fournissent tous un contrôle précis sur la planification et l’exécution de la tâche. Une instance de fabrique qui cible le planificateur de tâches actuel est disponible en tant que propriété statique (Factory) de la Task classe ; par exemple :
Task.Factory.StartNew(…)
.Utilisez les constructeurs du
Task
type et de laStart
méthode si vous souhaitez générer et planifier la tâche séparément. Les méthodes publiques doivent retourner uniquement les tâches qui ont déjà été lancées.Utilisez les surcharges de la méthode Task.ContinueWith. Cette méthode crée une tâche planifiée quand une autre tâche est terminée. Certaines surcharges ContinueWith acceptent un jeton d’annulation, des options de continuation et un planificateur de tâches pour un meilleur contrôle de la planification et de l’exécution de la tâche de continuation.
Utilisez les méthodes TaskFactory.ContinueWhenAll et TaskFactory.ContinueWhenAny. Ces méthodes créent une tâche planifiée lorsque l’ensemble ou l’un des ensembles de tâches fournis est terminé. Ces méthodes fournissent également des surcharges pour contrôler la planification et l’exécution de ces tâches.
Dans les tâches liées au calcul, le système peut empêcher l’exécution d’une tâche planifiée s’il reçoit une demande d’annulation avant de commencer à exécuter la tâche. Par conséquent, si vous fournissez un jeton d’annulation (CancellationToken objet), vous pouvez transmettre ce jeton au code asynchrone qui surveille le jeton. Vous pouvez également fournir le jeton à l’une des méthodes mentionnées précédemment, telles que StartNew
ou Run
pour que le Task
runtime puisse également surveiller le jeton.
Par exemple, considérez une méthode asynchrone qui restitue une image. Le corps de la tâche peut interroger le jeton d’annulation pour que l’exécution du code puisse s’arrêter plus tôt si une demande d’annulation arrive pendant le rendu. En outre, si la demande d’annulation arrive avant le début du rendu, vous devez empêcher l’opération de rendu :
internal Task<Bitmap> RenderAsync(
ImageData data, CancellationToken cancellationToken)
{
return Task.Run(() =>
{
var bmp = new Bitmap(data.Width, data.Height);
for(int y=0; y<data.Height; y++)
{
cancellationToken.ThrowIfCancellationRequested();
for(int x=0; x<data.Width; x++)
{
// render pixel [x,y] into bmp
}
}
return bmp;
}, cancellationToken);
}
Friend Function RenderAsync(data As ImageData, cancellationToken As _
CancellationToken) As Task(Of Bitmap)
Return Task.Run(Function()
Dim bmp As New Bitmap(data.Width, data.Height)
For y As Integer = 0 to data.Height - 1
cancellationToken.ThrowIfCancellationRequested()
For x As Integer = 0 To data.Width - 1
' render pixel [x,y] into bmp
Next
Next
Return bmp
End Function, cancellationToken)
End Function
Les tâches liées au calcul se terminent dans un Canceled état si au moins une des conditions suivantes est remplie :
Une demande d’annulation arrive via l’objet CancellationToken , qui est fourni en tant qu’argument de la méthode de création (par exemple,
StartNew
ouRun
) avant la transition de la tâche vers l’état Running .Une OperationCanceledException exception n’est pas gérée dans le corps d’une telle tâche ; cette exception contient le même CancellationToken que celui transmis à la tâche, et ce jeton montre que l’annulation est demandée.
Si une autre exception n’est pas gérée dans le corps de la tâche, la tâche se termine par l’état Faulted et toute tentative d’attente sur la tâche ou d’accès à son résultat entraîne la levée d’une exception.
Tâches liées aux E/S
Pour créer une tâche qui ne doit pas être directement sauvegardée par un thread pour l’intégralité de son exécution, utilisez le TaskCompletionSource<TResult> type. Ce type expose une Task propriété qui retourne une instance associée Task<TResult> . Le cycle de vie de cette tâche est contrôlé par TaskCompletionSource<TResult> des méthodes telles que SetResult, SetException, SetCanceledet leurs TrySet
variantes.
Supposons que vous souhaitez créer une tâche qui se termine après une période spécifiée. Par exemple, vous pouvez retarder une activité dans l’interface utilisateur. La System.Threading.Timer classe offre déjà la possibilité d’appeler de manière asynchrone un délégué après une période de temps spécifiée, et en utilisant TaskCompletionSource<TResult> vous pouvez mettre un Task<TResult> front sur le minuteur, par exemple :
public static Task<DateTimeOffset> Delay(int millisecondsTimeout)
{
TaskCompletionSource<DateTimeOffset> tcs = null;
Timer timer = null;
timer = new Timer(delegate
{
timer.Dispose();
tcs.TrySetResult(DateTimeOffset.UtcNow);
}, null, Timeout.Infinite, Timeout.Infinite);
tcs = new TaskCompletionSource<DateTimeOffset>(timer);
timer.Change(millisecondsTimeout, Timeout.Infinite);
return tcs.Task;
}
Public Function Delay(millisecondsTimeout As Integer) As Task(Of DateTimeOffset)
Dim tcs As TaskCompletionSource(Of DateTimeOffset) = Nothing
Dim timer As Timer = Nothing
timer = New Timer(Sub(obj)
timer.Dispose()
tcs.TrySetResult(DateTimeOffset.UtcNow)
End Sub, Nothing, Timeout.Infinite, Timeout.Infinite)
tcs = New TaskCompletionSource(Of DateTimeOffset)(timer)
timer.Change(millisecondsTimeout, Timeout.Infinite)
Return tcs.Task
End Function
La Task.Delay méthode est fournie à cet effet et vous pouvez l’utiliser à l’intérieur d’une autre méthode asynchrone, par exemple pour implémenter une boucle d’interrogation asynchrone :
public static async Task Poll(Uri url, CancellationToken cancellationToken,
IProgress<bool> progress)
{
while(true)
{
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
bool success = false;
try
{
await DownloadStringAsync(url);
success = true;
}
catch { /* ignore errors */ }
progress.Report(success);
}
}
Public Async Function Poll(url As Uri, cancellationToken As CancellationToken,
progress As IProgress(Of Boolean)) As Task
Do While True
Await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken)
Dim success As Boolean = False
Try
await DownloadStringAsync(url)
success = true
Catch
' ignore errors
End Try
progress.Report(success)
Loop
End Function
La TaskCompletionSource<TResult> classe n’a pas d’équivalent non générique. Toutefois, Task<TResult> dérive de Task, de sorte que vous pouvez utiliser l’objet générique TaskCompletionSource<TResult> pour les méthodes liées aux E/S qui retournent simplement une tâche. Pour ce faire, vous pouvez utiliser une source avec un TResult
factice (Boolean est un bon choix par défaut, mais si vous avez peur que l'utilisateur de la Task en fasse une classe de base Task<TResult>, vous pouvez utiliser un type TResult
privé à la place). Par exemple, la Delay
méthode de l’exemple précédent retourne l’heure actuelle avec le décalage obtenu (Task<DateTimeOffset>
). Si une telle valeur de résultat n’est pas nécessaire, la méthode peut plutôt être codée comme suit (notez le changement de type de retour et le changement d’argument en TrySetResult) :
public static Task<bool> Delay(int millisecondsTimeout)
{
TaskCompletionSource<bool> tcs = null;
Timer timer = null;
timer = new Timer(delegate
{
timer.Dispose();
tcs.TrySetResult(true);
}, null, Timeout.Infinite, Timeout.Infinite);
tcs = new TaskCompletionSource<bool>(timer);
timer.Change(millisecondsTimeout, Timeout.Infinite);
return tcs.Task;
}
Public Function Delay(millisecondsTimeout As Integer) As Task(Of Boolean)
Dim tcs As TaskCompletionSource(Of Boolean) = Nothing
Dim timer As Timer = Nothing
Timer = new Timer(Sub(obj)
timer.Dispose()
tcs.TrySetResult(True)
End Sub, Nothing, Timeout.Infinite, Timeout.Infinite)
tcs = New TaskCompletionSource(Of Boolean)(timer)
timer.Change(millisecondsTimeout, Timeout.Infinite)
Return tcs.Task
End Function
Tâches liées au calcul mixte et liées aux E/S
Les méthodes asynchrones ne sont pas limitées uniquement aux opérations liées au calcul ou aux E/S, mais peuvent représenter un mélange des deux. En fait, plusieurs opérations asynchrones sont souvent combinées en opérations mixtes plus volumineuses. Par exemple, la RenderAsync
méthode d’un exemple précédent a effectué une opération nécessitant beaucoup de calcul pour afficher une image en fonction de certaines entrées imageData
. Cela imageData
peut provenir d’un service web auquel vous accédez de manière asynchrone :
public async Task<Bitmap> DownloadDataAndRenderImageAsync(
CancellationToken cancellationToken)
{
var imageData = await DownloadImageDataAsync(cancellationToken);
return await RenderAsync(imageData, cancellationToken);
}
Public Async Function DownloadDataAndRenderImageAsync(
cancellationToken As CancellationToken) As Task(Of Bitmap)
Dim imageData As ImageData = Await DownloadImageDataAsync(cancellationToken)
Return Await RenderAsync(imageData, cancellationToken)
End Function
Cet exemple montre également comment un jeton d’annulation unique peut être threadé via plusieurs opérations asynchrones. Pour plus d’informations, consultez la section Utilisation de l’annulation dans Consommation du modèle asynchrone basé sur les tâches.