Как можно изменить планировщик Task. Run?Можно изменить Run?

#c# #unity3d #task

#c# #unity-игровой движок #задача

Вопрос:

Мне нужно запустить несколько задач, которые требуют запуска с использованием определенного планировщика, поскольку в противном случае они завершатся неудачей, поскольку объекты, которые они создают, могут быть созданы только из определенного потока.

Использование Task.Factory.StartNew работает хорошо, единственное, синтаксис немного громоздкий.

Итак, мне пришла в голову идея написать метод расширения, который позволил бы мне сохранить краткий синтаксис Task.Run , но указать другой планировщик, отличный от TaskScheduler.Default . Однако мне трудно понять, как написать такой метод расширения.

Вопрос:

Как я могу изменить планировщик, с Task.Run которым он предназначен для запуска, если это вообще возможно?

Пример кода:

 using System;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

public class NewBehaviourScript : MonoBehaviour
{
    private async void Test()
    {
        // game objects can only be created from Unity main thread, get its scheduler

        var scheduler = TaskScheduler.FromCurrentSynchronizationContext();

        // 1. syntax using factory, works but is a bit cumbersome

        await Task.Factory.StartNew(
            () => new GameObject("test"),
            CancellationToken.None,
            TaskCreationOptions.None,
            scheduler
        );

        // 2. ideal syntax though it will fail since it'll run with the wrong scheduler

        await Task.Run(() => new GameObject("test"));

        // 3. ideal syntax but how to implement .Schedule method?

        await Task.Run(() => new GameObject("test")).Schedule(scheduler);
    }
}
  

Метод расширения:

 public static class Extensions
{
    public static Task<T> Schedule<T>(this Task<T> task, TaskScheduler scheduler)
    {
        // how, if possible at all to write this method?

        throw new NotImplementedException();
    }
}
  

Комментарии:

1. нет необходимости использовать StartNew для запуска задачи, используя текущий контекст синхронизации. Если вы хотите запустить код, используя текущий контекст синхронизации, просто запустите код , нет необходимости в другой задаче. В этом случае ваш код уже находится в главном потоке, иначе планировщик текущего контекста не передал бы его вам.

Ответ №1:

Чего вы хотите, так это TaskFactory . Просто создайте один с желаемым TaskScheduler . Кроме того, вы обычно не хотите использовать настройки параметров по умолчанию. Предполагая, что вы используете задачи с await , обычно требуется по крайней мере DenyChildAttach :

 var factory = new TaskFactory(CancellationToken.None,
    TaskCreationOptions.DenyChildAttach,
    TaskContinuationOptions.DenyChildAttach | TaskContinuationOptions.ExecuteSynchronously,
    scheduler);
  

После создания (один раз) вы можете использовать factory.StartNew для постановки работы в очередь без необходимости передавать все параметры каждый раз.

Комментарии:

1. Не мог ожидать лучшего, ответ от Гуру асинхронности, спасибо 🙂

2. Как насчет пропустить TaskFactory и просто написать это: Task.CompletedTask.ContinueWith( t => DoWork(), scheduler ) ?

3. @HappyNomad: Смысл TaskFactory в том, что вам не нужно повторять все параметры каждый раз, когда вы хотите запустить задачу.

4. @StephenCleary В исходном вопросе задается Task.Run поведение, за исключением другого scheduler . Я думаю, что это Task.CompletedTask.ContinueWith лучший выбор, поскольку вам ни разу не нужно указывать те параметры, о которых вам неинтересно думать или изменять.

5. @StephenCleary Я только что заметил, что ContinueWith по умолчанию используется TaskContinuationOptions.None . Я думаю, вы бы порекомендовали отказаться от такого ContinueWIth поведения по умолчанию? В конце концов, это оставило бы TaskFactory лучшим вариантом.

Ответ №2:

Вы просто не можете этого сделать. К тому времени, когда вы получите Task ответ от Task.Run , задача уже запланирована в планировщике по умолчанию и, возможно, уже запущена. Если вы хотите использовать пользовательский планировщик, не используйте Task.Run . Либо используйте Task.Factory.StartNew , либо напишите свой собственный метод для планирования новой задачи определенным образом.