2

У меня простой вопрос.

Учусь работать с Task'ами.

Самый простой пример обычно такой:

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
            var t = Task.Run(() => { Console.WriteLine(DateTime.Now.ToLongTimeString()); Thread.Sleep(2000); });
            t.Wait();
            Console.WriteLine("That's all!");
        }
    }

Но тут всё в лямбда - выражении, что не всегда удобно. Ок, можно написать так:

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        var t1 = new Task(DoSomething);
        t1.Start();

        t1.Wait();
        Console.WriteLine("That's all!");
    }

    static void DoSomething() {
        Console.WriteLine($"Hi at {DateTime.Now.ToLongTimeString()}");
        Thread.Sleep(2000);
    }
}

Здесь я отделил момент создания таски от её запуска, что иногда может быть очень удобно. Но, как только появляется аргумент, это перестаёт работать:

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        var t1 = new Task(DoSomething);
        t1.Start();
        var xxx = 42;

        t1.Wait();
        Console.WriteLine("That's all!");
    }

    static void DoSomething(int x) {
        Console.WriteLine($"x = {x} at {DateTime.Now.ToLongTimeString()}");
        Thread.Sleep(2000);
    }
}

В строке var t1 = new Task(DoSomething) я получаю ошибку Error CS1503 Argument 1: cannot convert from 'method group' to 'System.Action'

Как можно создать задачу с аргументом, передающимся в неё?

Спасибо!

Дополнение я нашел вот такое - довольно понятное - объяснение.

Но там тоже пример какой то ущербный, компилятор ругается на него "Делегат "Action" не принимает аргументы 1." - вообще непонятная ошибка

M.O.
  • 53
  • Первая строчка в Яндексе по запросу "c# task with parameters": https://stackoverflow.com/questions/30225476/task-run-with-parameters – Павел Ериков stand with Russia Sep 08 '23 at 12:13
  • я читал этот вопрос-ответ... может, я чего то не понял, но там разбирается случай "запустить задачу с аргументом через Task.Run()". А я хочу создать задачу, а запустить её в другом месте кода. – M.O. Sep 08 '23 at 12:18
  • 1
    Там есть и не только через Run в ответах. Можно создать Action, а потом передавать его в Task, вот примеры: https://learn.microsoft.com/ru-ru/dotnet/api/system.threading.tasks.task?view=net-7.0 Сам конструктор Task принимает Action, который по той же документации: инкапсулирует метод, который не имеет параметров и не возвращает значений. По этому, создать задачу так как вы хотите, вероятно не получится. – Павел Ериков stand with Russia Sep 08 '23 at 12:46
  • я, прежде чем написать вопрос, долго, часа два, мучился с этим Action, и я не понимаю синтаксис. я понимаю идею, но не понимаю, как это написать, чтобы компилятор не ругался. И всё таки, в задачу как то ведь можно передать аргументы? иначе вряд ли бы Task'и так широко использовались. Вопрос про Action: вот у меня уже есть процедура void DoSomething(int x){ ... } - а как мне из неё сделать Action? – M.O. Sep 08 '23 at 12:48
  • Вообще у меня было ОЧЕНЬ сложное решение для данной проблемы. Если подождёте, то я его найду (или восстановлю) – Швеев Алексей Sep 08 '23 at 13:28
  • @ШвеевАлексей - спасибо, может быть, даже не стОит его восстанавливать, если оно такое сложное. я хочу просто освоить базовый синтаксис, подготовиться к собесам. – M.O. Sep 08 '23 at 13:39
  • 1
    Да не, я использовал решение для другой задачи, а здесь можно сделать всё в сто раз проще парой строк. Просто сразу не дошло почему то) Сейчас опубликую ответ – Швеев Алексей Sep 08 '23 at 13:43

2 Answers2

1

Оказалось решение довольно простым:

public static class PrTask
{
    public static Task Run(MulticastDelegate @delegate, params object[] args) => 
        Task.Run(() => @delegate.DynamicInvoke(args));
public static Task Gen(MulticastDelegate @delegate, params object[] args) =>
    new Task(() => @delegate.DynamicInvoke(args));

public static Action WithParams(this MulticastDelegate @delegate, params object[] args) =>
    () => @delegate.DynamicInvoke(args);

}

Пример использования:

// Метод можно объявлять как угодно
var method = (string x, string y) => Console.WriteLine(x + y);

PrTask.Run(method, "Runnig task like Task.Run()", ".").Wait();

var task = PrTask.Gen(method, "Runnig task by creating Task object", "."); task.Start(); task.Wait();

var task2 = new Task(method.WithParams("gen Action from delegate", ".")); task2.Start(); task2.Wait();

0

Всё таки я смог сделать это!

Спасибо всем, кто написал мне подсказки в комментариях.

Прикол был в том, что в создании Action не используется опаретор new() - раньше мне в NET такое не встречалось.

полностью решение выглядит так:

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        object xxx = 42; // аргумент, с которым я хочу вызвать функцию void DoSomething(int x)

        Action<object> myAction = (object obj) =>   // "переходник". Главное - не использовать new() при создании Action!
        {
            DoSomething((int)obj);
        };

        var t1 = new Task(myAction, xxx);
        t1.Start();


        t1.Wait();
        Console.WriteLine("That's all!");
    }

    static void DoSomething(int x) {
        Console.WriteLine($"x = {x} at {DateTime.Now.ToLongTimeString()}");
        Thread.Sleep(2000);
    }
}

M.O.
  • 53
  • 2
    И всё-таки откажитесь от использования new Task. Нет смысла в предварительном создании таски в холодном состоянии. Передавайте или параметры, или готовый делегат (Action) и вызывайте Task.Run. – Alexander Petrov Sep 08 '23 at 13:35
  • А если мне надо периодически вызывать Task? Один и тот же? Вообще, главные страдания - именно так, страдания! - от отсутствия полного примера кода. Потому что придумать такой синтакис - нельзя... и получается: я понимаю, что надо сделать, а написать - не могу.... – M.O. Sep 08 '23 at 13:36
  • Ну вы попробуйте в конце своего кода ещё раз вызвать t1.Start(); – Alexander Petrov Sep 08 '23 at 13:42
  • @AlexanderPetrov - спасибо! Да, действительно, "Start may not be called on a task that has completed". Этого я не заметил. Получается, Task хорошо бы создавать каждый раз перед запуском? – M.O. Sep 08 '23 at 13:46
  • 2
    Да, после завершения таски с ней ничего больше нельзя делать. Только создавать новую. И проще всего - с помощью Task.Run. – Alexander Petrov Sep 08 '23 at 13:47
  • и всё таки: а что делать, если мне надо раз за разом вызывать одну и ту же задачу? Мне в голову приходит только два пути: 1) крутиться в цикле 2) использовать ContinueWith() - и этот метод выглядит... покультурнее. – M.O. Sep 08 '23 at 14:14
  • Конструктор new Task для запуска потока устарел, и его не следует использовать. Использовать следует только Task.Run 2) В многопоточке смысла передавать в поток аргументы на старте как в метод, практически всегда нет. Почему? Почитайте про шаблон проектирования Producer/Consumer. 3) Для всего остального есть лямбда - это хорошо, красиво и надёжно. Другими словами, вы нашли решение, которое на практике вам никогда не пригодится. И выглядит оно некрасиво. Решайте настоящие практические задачи, а не вот это вот всё. И главное: Task это не поток, Task - это ожидалка, а не выполнялка. – aepot Sep 08 '23 at 20:44
  • С другой стороны, сам по себе Task как ожидалка, используется для асинхронного программирования чаще, чем в контексте многопоточки, и да - это не одно и то же. В итоге, вы ни у кого не увидите в серьёзном коде task.Wait();, а увидите await task;. Так что добро пожаловать в асинхронный мир. Хотите изучать Task - начните с async/await, таски там будут на каждом углу, но нигде не будет ни task.Wait, не Task.Run. Что касается создания делегата Action, new там вызовется сам, хотите вы того или нет. И кстати в Action у вас та же лямбда. – aepot Sep 08 '23 at 20:50
  • а что делать, если мне надо раз за разом вызывать одну и ту же задачу? - Producer/Consumer. Если кратко, то цикл внутри потока, да, с каналом передачи данных в него. Начните с BlockingCollection. https://ru.stackoverflow.com/a/1123495/373567 – aepot Sep 08 '23 at 20:55