我有一些返回Task<T>
的方法,我可以随意await
。我希望在自定义TaskScheduler
上执行这些任务而不是默认任务。
var task = GetTaskAsync ();
await task;
我知道我可以创建一个新的TaskFactory (new CustomScheduler ())
并从中StartNew ()
执行StartNew ()
,但是Task
会采取行动并创建Task
,我已经拥有了TaskCompletionSource
{1}}(由TaskScheduler
如何为await
指定自己的{{1}}?
答案 0 :(得分:39)
我认为您真正想要的是Task.Run
,但使用自定义调度程序。 StartNew
不能直观地使用异步方法; Stephen Toub有一篇关于the differences between Task.Run
and TaskFactory.StartNew
的精彩博文。
因此,要创建自己的自定义Run
,您可以执行以下操作:
private static readonly TaskFactory myTaskFactory = new TaskFactory(
CancellationToken.None, TaskCreationOptions.DenyChildAttach,
TaskContinuationOptions.None, new MyTaskScheduler());
private static Task RunOnMyScheduler(Func<Task> func)
{
return myTaskFactory.StartNew(func).Unwrap();
}
private static Task<T> RunOnMyScheduler<T>(Func<Task<T>> func)
{
return myTaskFactory.StartNew(func).Unwrap();
}
private static Task RunOnMyScheduler(Action func)
{
return myTaskFactory.StartNew(func);
}
private static Task<T> RunOnMyScheduler<T>(Func<T> func)
{
return myTaskFactory.StartNew(func);
}
然后,您可以在自定义调度程序上执行同步或异步方法。
答案 1 :(得分:9)
构造TaskCompletionSource<T>.Task
时没有任何操作和调度程序
是在第一次致电ContinueWith(...)
(来自Asynchronous Programming with the Reactive Framework and the Task Parallel Library — Part 3)时分配的。
值得庆幸的是,您可以通过实现从INotifyCompletion
派生自己的类,然后以类似于await SomeTask.ConfigureAwait(false)
的模式使用它来配置任务应该在OnCompleted(Action continuation)
开始使用的调度程序,从而稍微自定义await行为。 {1}}方法(来自await anything;)。
以下是用法:
TaskCompletionSource<object> source = new TaskCompletionSource<object>();
public async Task Foo() {
// Force await to schedule the task on the supplied scheduler
await SomeAsyncTask().ConfigureScheduler(scheduler);
}
public Task SomeAsyncTask() { return source.Task; }
以下是使用任务扩展方法的ConfigureScheduler
的简单实现,其中包含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() { }
}
}
这是一个可以作为控制台应用程序编译的工作示例:
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace Example {
class Program {
static TaskCompletionSource<object> source = new TaskCompletionSource<object>();
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<Task> GetScheduledTasks() { yield break; }
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { return false; }
protected override void QueueTask(Task task) {
TryExecuteTask(task);
}
}
}
答案 2 :(得分:3)
在评论之后,您似乎想要控制运行await之后代码的调度程序。
编译器默认从当前SynchronizationContext上运行的await创建一个延续。因此,最好的方法是在呼叫等待之前设置SynchronizationContext
。
有一些方法可以等待特定的上下文。请参阅Jon Skeet的Configure Await,特别是有关SwitchTo的部分,了解有关如何实现此类内容的更多信息。
编辑: TaskEx中的SwitchTo方法已被删除,因为它太容易被滥用。请参阅MSDN Forum了解原因。
答案 3 :(得分:3)
您是否适合此方法调用:
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);
答案 4 :(得分:1)
无法将丰富的异步功能嵌入到自定义TaskScheduler
中。此类设计时并未考虑async
/ await
。使用自定义TaskScheduler
的标准方法是作为Task.Factory.StartNew
方法的参数。此方法不了解异步委托。可以提供一个异步委托,但是它将被视为返回某些结果的任何其他委托。要获得异步委托的实际等待结果,必须对返回的任务调用Unwrap()
。但这不是问题。问题在于TaskScheduler
基础结构未将异步委托视为单个工作单元。它将每个任务分成多个迷你任务(使用每个await
作为分隔符),并且每个迷你任务都被单独处理。这严重限制了可以在此类顶部实现的异步功能。例如,下面是一个自定义TaskScheduler
,该自定义public class MyTaskScheduler : TaskScheduler
{
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);
protected async override void QueueTask(Task task)
{
await _semaphore.WaitAsync();
try
{
await Task.Run(() => base.TryExecuteTask(task));
}
finally
{
_semaphore.Release();
}
}
protected override bool TryExecuteTaskInline(Task task,
bool taskWasPreviouslyQueued) => base.TryExecuteTask(task);
protected override IEnumerable<Task> GetScheduledTasks() { yield break; }
}
用于一次将提供的任务排队(以限制并发性):
SemaphoreSlim
Task
应该确保一次只能运行一个Task
。不幸的是,它不起作用。信号量过早释放,因为在调用QueueTask(task)
中传递的await
不是代表异步委托的全部工作的任务,而是代表直到第一个TryExecuteTaskInline
为止的部分。其他部分将传递给var taskScheduler = new MyTaskScheduler();
var tasks = Enumerable.Range(1, 5).Select(n => Task.Factory.StartNew(async () =>
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} Item {n} Started");
await Task.Delay(1000);
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} Item {n} Finished");
}, default, TaskCreationOptions.None, taskScheduler))
.Select(t => t.Unwrap())
.ToArray();
Task.WaitAll(tasks);
方法。由于没有提供标识符或其他机制,因此无法关联这些任务部分。这是实际发生的情况:
TaskScheduler
输出:
05:29:58.346项目1已开始
05:29:58.358项目2已开始
05:29:58.358项目3已开始
05:29:58.358项目4已开始
05:29:58.358项目5已开始
05:29:59.358项目1已完成
05:29:59.374项目5已完成
05:29:59.374项目4已完成
05:29:59.374项目2已完成
05:29:59.374项目3已完成
灾难,所有任务都立即排队。
结论:在需要高级异步功能时,自定义{{1}}类是不可行的。
答案 5 :(得分:-1)
面对同样的问题,尝试使用LimitedConcurrencyLevelTaskScheduler,但它不支持异步任务。所以...
刚刚编写了我自己的小型简单调度程序,它允许运行基于全局 ThreadPool(和 Task.Run 方法)的异步任务,并具有限制当前最大并行度的能力。这对于我的确切目的来说已经足够了,也许也会帮助你们,伙计们。
主要演示代码(控制台应用程序,dotnet core 3.1):
static async Task Main(string[] args)
{
//5 tasks to run per time
int concurrentLimit = 5;
var scheduler = new ThreadPoolConcurrentScheduler(concurrentLimit);
//catch all errors in separate event handler
scheduler.OnError += Scheduler_OnError;
// just monitor "live" state and output to console
RunTaskStateMonitor(scheduler);
// simulate adding new tasks "on the fly"
SimulateAddingTasksInParallel(scheduler);
Console.WriteLine("start adding 50 tasks");
//add 50 tasks
for (var i = 1; i <= 50; i++)
{
scheduler.StartNew(myAsyncTask);
}
Console.WriteLine("50 tasks added to scheduler");
Thread.Sleep(1000000);
}
支持代码(放在同一个地方):
private static void Scheduler_OnError(Exception ex)
{
Console.WriteLine(ex.ToString());
}
private static int currentTaskFinished = 0;
//your sample of async task
static async Task myAsyncTask()
{
Console.WriteLine("task started ");
using (HttpClient httpClient = new HttpClient())
{
//just make http request to ... wikipedia!
//sorry, Jimmy Wales! assume,guys, you will not DDOS wiki :)
var uri = new Uri("https://wikipedia.org/");
var response = await httpClient.GetAsync(uri);
string result = await response.Content.ReadAsStringAsync();
if (string.IsNullOrEmpty(result))
Console.WriteLine("error, await is not working");
else
Console.WriteLine($"task result : site length is {result.Length}");
}
//or simulate it using by sync sleep
//Thread.Sleep(1000);
//and for tesing exception :
//throw new Exception("my custom error");
Console.WriteLine("task finished ");
//just incrementing total ran tasks to output in console
Interlocked.Increment(ref currentTaskFinished);
}
static void SimulateAddingTasksInParallel(ThreadPoolConcurrentScheduler taskScheduler)
{
int runCount = 0;
Task.Factory.StartNew(() =>
{
while (true)
{
runCount++;
if (runCount > 5)
break;
//every 10 sec 5 times
Thread.Sleep(10000);
//adding new 5 tasks from outer task
Console.WriteLine("start adding new 5 tasks!");
for (var i = 1; i <= 5; i++)
{
taskScheduler.StartNew(myAsyncTask);
}
Console.WriteLine("new 5 tasks added!");
}
}, TaskCreationOptions.LongRunning);
}
static void RunTaskStateMonitor(ThreadPoolConcurrentScheduler taskScheduler)
{
int prev = -1;
int prevQueueSize = -1;
int prevFinished = -1;
Task.Factory.StartNew(() =>
{
while (true)
{
// getting current thread count in working state
var currCount = taskScheduler.GetCurrentWorkingThreadCount();
// getting inner queue state
var queueSize = taskScheduler.GetQueueTaskCount();
//just output overall state if something changed
if (prev != currCount || queueSize != prevQueueSize || prevFinished != currentTaskFinished)
{
Console.WriteLine($"Monitor : running tasks:{currCount}, queueLength:{queueSize}. total Finished tasks : " + currentTaskFinished);
prev = currCount;
prevQueueSize = queueSize;
prevFinished = currentTaskFinished;
}
// check it every 10 ms
Thread.Sleep(10);
}
}
, TaskCreationOptions.LongRunning);
}
调度程序:
public class ThreadPoolConcurrentScheduler
{
private readonly int _limitParallelThreadsCount;
private int _threadInProgressCount = 0;
public delegate void onErrorDelegate(Exception ex);
public event onErrorDelegate OnError;
private ConcurrentQueue<Func<Task>> _taskQueue;
private readonly object _queueLocker = new object();
public ThreadPoolConcurrentScheduler(int limitParallelThreadsCount)
{
//set maximum parallel tasks to run
_limitParallelThreadsCount = limitParallelThreadsCount;
// thread-safe queue to store tasks
_taskQueue = new ConcurrentQueue<Func<Task>>();
}
//main method to start async task
public void StartNew(Func<Task> task)
{
lock (_queueLocker)
{
// checking limit
if (_threadInProgressCount >= _limitParallelThreadsCount)
{
//waiting new "free" threads in queue
_scheduleTask(task);
}
else
{
_startNewTask(task);
}
}
}
private void _startNewTask(Func<Task> task)
{
Interlocked.Increment(ref _threadInProgressCount);
Task.Run(async () =>
{
try
{
await task();
}
catch (Exception e)
{
//Console.WriteLine(e);
OnError?.Invoke(e);
}
}).ContinueWith(_onTaskEnded);
}
//will be called on task end
private void _onTaskEnded(Task task)
{
lock (_queueLocker)
{
Interlocked.Decrement(ref _threadInProgressCount);
//queue has more priority, so if thread is free - let's check queue first
if (!_taskQueue.IsEmpty)
{
if (_taskQueue.TryDequeue(out var result))
{
_startNewTask(result);
}
}
}
}
private void _scheduleTask(Func<Task> task)
{
_taskQueue.Enqueue(task);
}
//returning in progress task count
public int GetCurrentWorkingThreadCount()
{
return _threadInProgressCount;
}
//return number of tasks waiting to run
public int GetQueueTaskCount()
{
lock (_queueLocker) return _taskQueue.Count;
}
}
一些注意事项: