#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
, либо напишите свой собственный метод для планирования новой задачи определенным образом.