Comment exécuter une tâche sur un TaskScheduler personnalisé en utilisant wait?

J’ai quelques méthodes de retour de Task sur lesquelles je peux await à volonté. J’aimerais que ces tâches soient exécutées sur un TaskScheduler personnalisé TaskScheduler lieu de celui par défaut.

 var task = GetTaskAsync (); await task; 

Je sais que je peux créer une nouvelle TaskFactory (new CustomScheduler ()) et en faire un StartNew () , mais StartNew () effectue une action et crée la Task , et j’ai déjà la Task (renvoyée dans les coulisses par un object TaskCompletionSource ).

Comment puis-je spécifier mon propre TaskScheduler pour await ?

Je pense que ce que vous voulez vraiment, c’est faire un Task.Run , mais avec un planificateur personnalisé. StartNew ne fonctionne pas intuitivement avec les méthodes asynchrones; Stephen Toub a publié un excellent article sur les différences entre Task.Run et TaskFactory.StartNew .

Donc, pour créer votre propre Run personnalisé, vous pouvez faire quelque chose comme ceci:

 private static readonly TaskFactory myTaskFactory = new TaskFactory( CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskContinuationOptions.None, new MyTaskScheduler()); private static Task RunOnMyScheduler(Func func) { return myTaskFactory.StartNew(func).Unwrap(); } private static Task RunOnMyScheduler(Func> func) { return myTaskFactory.StartNew(func).Unwrap(); } private static Task RunOnMyScheduler(Action func) { return myTaskFactory.StartNew(func); } private static Task RunOnMyScheduler(Func func) { return myTaskFactory.StartNew(func); } 

Ensuite, vous pouvez exécuter des méthodes synchrones ou asynchrones sur votre planificateur personnalisé.

La TaskCompletionSource.Task est construite sans aucune action et le planificateur est atsortingbué lors du premier appel à ContinueWith(...) (à partir de la programmation asynchrone avec le cadre réactif et la bibliothèque parallèle de tâches – Partie 3 ).

Heureusement, vous pouvez personnaliser légèrement le comportement de l’attente en implémentant votre propre classe dérivée de INotifyCompletion , puis en l’utilisant dans un modèle similaire à await SomeTask.ConfigureAwait(false) pour configurer le planificateur que la tâche doit commencer à utiliser dans la OnCompleted(Action continuation) (de rien attendre; ).

Voici l’usage:

  TaskCompletionSource source = new TaskCompletionSource(); public async Task Foo() { // Force await to schedule the task on the supplied scheduler await SomeAsyncTask().ConfigureScheduler(scheduler); } public Task SomeAsyncTask() { return source.Task; } 

Voici une implémentation simple de ConfigureScheduler utilisant une méthode d’extension de tâche avec la partie importante de OnCompleted :

 public static class TaskExtension { public static CustomTaskAwaitable ConfigureScheduler(this Task task, TaskScheduler scheduler) { return new CustomTaskAwaitable(task, scheduler); } } public struct CustomTaskAwaitable { CustomTaskAwaiter awaitable; public CustomTaskAwaitable(Task task, TaskScheduler scheduler) { awaitable = new CustomTaskAwaiter(task, scheduler); } public CustomTaskAwaiter GetAwaiter() { return awaitable; } public struct CustomTaskAwaiter : INotifyCompletion { Task task; TaskScheduler scheduler; public CustomTaskAwaiter(Task task, TaskScheduler scheduler) { this.task = task; this.scheduler = scheduler; } public void OnCompleted(Action continuation) { // ContinueWith sets the scheduler to use for the continuation action task.ContinueWith(x => continuation(), scheduler); } public bool IsCompleted { get { return task.IsCompleted; } } public void GetResult() { } } } 

Voici un exemple de travail qui comstackra en tant qu’application console:

 using System; using System.Collections.Generic; using System.Runtime.ComstackrServices; using System.Threading.Tasks; namespace Example { class Program { static TaskCompletionSource source = new TaskCompletionSource(); static TaskScheduler scheduler = new CustomTaskScheduler(); static void Main(ssortingng[] args) { Console.WriteLine("Main Started"); var task = Foo(); Console.WriteLine("Main Continue "); // Continue Foo() using CustomTaskScheduler source.SetResult(null); Console.WriteLine("Main Finished"); } public static async Task Foo() { Console.WriteLine("Foo Started"); // Force await to schedule the task on the supplied scheduler await SomeAsyncTask().ConfigureScheduler(scheduler); Console.WriteLine("Foo Finished"); } public static Task SomeAsyncTask() { return source.Task; } } public struct CustomTaskAwaitable { CustomTaskAwaiter awaitable; public CustomTaskAwaitable(Task task, TaskScheduler scheduler) { awaitable = new CustomTaskAwaiter(task, scheduler); } public CustomTaskAwaiter GetAwaiter() { return awaitable; } public struct CustomTaskAwaiter : INotifyCompletion { Task task; TaskScheduler scheduler; public CustomTaskAwaiter(Task task, TaskScheduler scheduler) { this.task = task; this.scheduler = scheduler; } public void OnCompleted(Action continuation) { // ContinueWith sets the scheduler to use for the continuation action task.ContinueWith(x => continuation(), scheduler); } public bool IsCompleted { get { return task.IsCompleted; } } public void GetResult() { } } } public static class TaskExtension { public static CustomTaskAwaitable ConfigureScheduler(this Task task, TaskScheduler scheduler) { return new CustomTaskAwaitable(task, scheduler); } } public class CustomTaskScheduler : TaskScheduler { protected override IEnumerable GetScheduledTasks() { yield break; } protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { return false; } protected override void QueueTask(Task task) { TryExecuteTask(task); } } } 

Après les commentaires, il semble que vous souhaitiez contrôler le planificateur sur lequel le code après l’exécution de l’attente est exécuté.

La compilation crée une continuation à partir de l’attente qui s’exécute par défaut sur le SynchronizationContext actuel. Votre meilleure solution consiste donc à configurer le SynchronizationContext avant d’attendre l’appel.

Il y a plusieurs façons d’attendre un contexte spécifique. Voir Configurer l’attente de Jon Skeet, en particulier la partie sur SwitchTo, pour plus d’informations sur la manière d’implémenter quelque chose comme ceci.

EDIT: La méthode SwitchTo de TaskEx a été supprimée car trop facile à utiliser. Voir le forum MSDN pour des raisons.

Pouvez-vous vous adapter à cet appel de méthode:

  await Task.Factory.StartNew( () => { /* to do what you need */ }, CancellationToken.None, /* you can change as you need */ TaskCreationOptions.None, /* you can change as you need */ customScheduler);