Выполнение задач в общем виде

#c# #.net #.net-4.0 #task #task-parallel-library

#c# #.net #.net-4.0 #задача #задача-параллельная-библиотека

Вопрос:

В своем проекте я активно использую TPL и решаю разработать общий способ создания задач вместо явного вызова new Task(Action); new Task(Action<object>, object); или Task.Factory.StartNew(Action) т. Д. У меня будет такая функция, как

 void SpawnTask<A,B>( Action<A,B> action, A a, B b) { ... }
void SpawnTask<A,B,C>( Action<A,B,C> action, A a, B b, C c) { ... }
  

для создания и запуска задач.

Также мне нужно запускать обязательный метод после завершения каждой задачи. Таким образом, я должен обернуть фактический метод, который я хочу запустить, в другой метод, например

 void RuncTask(Action action)
{
  action();
  MandatoryMethod();
}
  

Пока я предлагаю два разных подхода.
Во-первых, использование анонимных делегатов:

 void SpawnTask<A,B>(Action<A,B> action, A a, B b)
{
  A dummyA = a; // To localize the parameters.
  B dummyB = b;
  var methodDelegate = delegate { action(dummyA,dummyB); };
  var taskDelegate = delegate { RunTask(methodDelegate); };
  Task.Factory.StartNew( taskDelegate );
}

void RuncTask(Action action)
{
  action();
  MandatoryMethod();
}
  

Во-вторых, использование кортежа:

 void SpawnTask<A,B>(Action<A,B> action, A a, B b)
{
  Tuple<Action<A,B>, A, B> tpl = Tuple.Create(action, a, b);
  Action<object> act = RunTask<A,B>;
  Task.Factory.StartNew( act, tpl );
}

void RuncTask<A,B>(object obj)
{
  var tpl = (Tuple<Action<A, B>, A, B>)param;
  tpl.Item1(tpl.Item2,tpl.Item3);
  MandatoryMethod();
}
  

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

Какой из них вы предпочитаете? Почему? Или какие другие решения у вас есть?

Ответ №1:

Ну, вы всегда можете создать метод расширения в Action<TA,TB> … и вы должны использовать продолжения, а не вызовы обернутых методов, чтобы реализовать поведение, при котором всегда выполняется другое действие при завершении задачи:

 public static class TaskExtensions
{
    public static void RunTask<TA,TB>( Action<TA,TB> action, TA a, TB b )
    {
        Task newTask = new Task( () => action(a,b) );
        newTask.ContinueWith( MandatoryMethod );
        newTask.Start();
    }

    // if you need to support other signature (more parameters) you would need to
    // create additional overloads of RunTask with more generic parameters...
    public static void RunTask( Action action );
    public static void RunTask<TA>( Action<TA> action, TA a );
    // etc ...

    private static void MandatoryMethod( Task t ) { /* your continuation logic */ }
}
  

Теперь вы можете использовать этот код следующим образом:

 public void SomeMethod( int x, int y ) { ... }

// later...
Action<int,int> myAction = SomeMethod;
myAction.RunTask( 1, 2 );
  

Обратите внимание, что в приведенной выше реализации метод продолжения ( MandatoryMethod ) будет выполняться асинхронно. Если вы хотите синхронного выполнения (как это реализовано в вашем примере), вы можете использовать перегрузку ContinueWith , которая принимает TaskContinuationOptions и передает TaskContinuationOptions.ExecuteSynchronously .

Ответ №2:

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

Что касается «обязательного метода», который необходимо выполнить после завершения задачи, посмотрите на продолжения (например Task.ContinueWith ).

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

1. Возможно, вопрос недостаточно ясен. Суть в том, чтобы создать задачу для всех типов void методов в общем виде.