How to run a Task on a custom TaskScheduler using await?

后端 未结 5 1850
暖寄归人
暖寄归人 2020-11-28 05:24

I have some methods returning Task on which I can await at will. I\'d like to have those Tasks executed on a custom TaskScheduler

5条回答
  •  执笔经年
    2020-11-28 06:00

    The TaskCompletionSource.Task is constructed without any action and the scheduler is assigned on the first call to ContinueWith(...) (from Asynchronous Programming with the Reactive Framework and the Task Parallel Library — Part 3).

    Thankfully you can customize the await behavior slightly by implementing your own class deriving from INotifyCompletion and then using it in a pattern similar to await SomeTask.ConfigureAwait(false) to configure the scheduler that the task should start using in the OnCompleted(Action continuation) method (from await anything;).

    Here is the 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; }
    
    
    

    Here is a simple implementation of ConfigureScheduler using a Task extension method with the important part in 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() { }
        }
    }
    

    Here's a working sample that will compile as a console application:

    using System;
    using System.Collections.Generic;
    using System.Runtime.CompilerServices;
    using System.Threading.Tasks;
    
    namespace Example {
        class Program {
            static TaskCompletionSource source = new TaskCompletionSource();
            static TaskScheduler scheduler = new CustomTaskScheduler();
    
            static void Main(string[] 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);
            }
        }
    }
    
        

    提交回复
    热议问题