О методе Task.StartNew (Action <Object>, Object)

2

Я изучаю TPL на этой странице, и один блок кода меня смущает.

Я читал эту страницу: Задача Parallelism (параллельная библиотека задач)

в одном разделе, он сказал, что следующий код является правильным решением, потому что лямбда в цикле не может получить значение, поскольку оно мутирует после каждой итерации, но окончательное значение. Поэтому вы должны обернуть "i" в объект CustomData. Код ниже:

class CustomData
{
   public long CreationTime;
   public int Name; 
   public int ThreadNum;
}

public class Example
{
   public static void Main()
   {
      // Create the task object by using an Action(Of Object) to pass in custom data 
      // to the Task constructor. This is useful when you need to capture outer variables 
      // from within a loop. 
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++)
      {
         taskArray[i] = Task.Factory.StartNew( (Object obj ) =>
            {
               CustomData data = obj as CustomData;
               if (data == null) 
                  return;

               data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
               Console.WriteLine("Task #{0} created at {1} on thread #{2}.", data.Name, data.CreationTime, data.ThreadNum);
            },
            new CustomData()
            {
               Name = i,
               CreationTime = DateTime.Now.Ticks
            });
      }
      Task.WaitAll(taskArray);     
   }
}

Код довольно прост и понятен, но здесь возникает моя проблема:

в методе Task.Factory.StartNew() автор использует одну из своих форм перегрузки:

Task.StartNew(Action<Object>, Object)

Мне так любопытно узнать , откуда происходит "obj"? Как у него есть 3 свойства: имя, CreationTime и ThreadNum.

Я искал через MSDN не нашел никакой полезной детали, но это: "Объект, содержащий данные, которые будут использоваться делегатом действия". MSDN Это ничего не объясняет.

Может ли кто-нибудь объяснить это?

Теги:
multithreading
task
task-parallel-library

2 ответа

2
Лучший ответ

Вот более краткий пример, который может помочь объяснить его.

void StartNew(Action<object> action, object o) {
  action(o);
}

Метод StartNew просто принимает делегат action и вызывает его, передавая o в качестве параметра. Значение, переданное лямбда - это просто значение, которое передается в StartNew после лямбда

// Prints "hello world"
StartNew(o => Console.WriteLine(o), "hello world");

В случае, если вы указали переданное значение, поскольку второй параметр

new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks} 

Это просто создает новый объект типа CustomData, присваивает ему некоторые свойства и делает его аргументом лямбда, определенным непосредственно перед ним. Это в конечном итоге станет значением obj в lambda

  • 0
    Один пример, чтобы управлять ими всеми! Это все объясняет. Огромное спасибо!
2

Это стандартный шаблон для передачи непрозрачного объекта состояния для обратного вызова, он используется во многих других местах в .NET framework. Более простой пример: SendOrPostCallback:

SynchronizationContext.Current.Post(state => 
    MessageBox.Show(state.ToString()), state: "Hello");

В дальнейшем будет вызываться lambda с обратным вызовом типа SendOrPostCallback с именем "Hello" как state.

Параметр state может использоваться как оптимизация, но это не обязательно, ни здесь, ни для Task.Factory.StartNew, либо в большинстве других случаев предоставляется аргумент state.

Ваша лямбда - это замыкание, которое имеет доступ к локальным переменным внешней области видимости, поэтому следующее приводит к идентичному результату, не передавая явно state:

var message = "Hello";
SynchronizationContext.Current.Post(_ => 
    MessageBox.Show(message), state: null);

То же самое относится к Task.Factory.StartNew. Для этой цели Task.Factory.StartNew предоставляет набор переопределений, которые принимают не общее действие Action, а не Action<object>.

Итак, ваш код может выглядеть следующим образом: IMO гораздо читабельнее:

  for (int i = 0; i < taskArray.Length; i++) {
     var data = new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks};

     taskArray[i] = Task.Factory.StartNew(() => 
     {
         if (data == null) 
           return;

         data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
         Console.WriteLine("Task #{0} created at {1} on thread #{2}.",
             data.Name, data.CreationTime, data.ThreadNum);
     });
  }
  • 1
    Спасибо за ваше объяснение :) Это действительно очень помогает.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню