Re[3]: Красивое решение для задачи с потоками
От: Sinix  
Дата: 18.03.15 16:36
Оценка:
Здравствуйте, Cynic, Вы писали:

C>Попробовал сделать как у Вас:


Не, это совсем не как у меня
Я завтра с утра напишу подробней, коротко: методы Task.Delay(), Task.Start() etc никак не влияют на текущий поток, они запускают новую задачу. Чтобы получить результат задачи — или task.Wait() / task.Result (блокирующее ожидание), или await.

например
        public async Task<IWorker> GetWorker()
        {
            Console.WriteLine("Worker {0} start", _name);

            await Task.Delay(_delay);

            Console.WriteLine("Worker {0} end", _name);
            return worker;
        }


Если метод асинхронный — он должен возвращать Task, чтобы было его дожидаться, например
        public static Task<IWorker> Start()
        {
            return Worker(_cts.Token);
        }


Я в каком-то из ответов приводил более-менее рабочий пример, начните с него. Если что непонятно — спрашивайте.

Сейчас сорри, занят, подробнее отвесу завтра.
Re[4]: Красивое решение для задачи с потоками
От: Cynic Россия  
Дата: 18.03.15 21:29
Оценка: -1
Здравствуйте, Sinix, Вы писали:

S>например

S>
S>        public async Task<IWorker> GetWorker()
S>        {
S>            Console.WriteLine("Worker {0} start", _name);

S>            await Task.Delay(_delay);

S>            Console.WriteLine("Worker {0} end", _name);
S>            return worker;
S>        }
S>


Тут я не подумал конечно, заменил на Thread.Sleep. Асинхронным метод делать не хочу, его задача возвращать таски. Мои таски не должны возвращать значение (void), т.к. предполагается что они изменяют поля класса в котором они созданы.

S>Если метод асинхронный — он должен возвращать Task, чтобы было его дожидаться, например

S>
S>        public static Task<IWorker> Start()
S>        {
S>            return Worker(_cts.Token);
S>        }
S>


Вы имеете ввиду, что по тому как я использовал это метод в Main он должен быть асинхронным? Но он же работает, что плохого в такой реализации?

Кстати в вашем примере я заметил, что вы объявляете метод как
static async Task Run(CancellationToken ct)

и даже вызываете его в Main
var task = Run(cts.Token);

но при этом в самом методе Run нет ни одного return Так и должно быть?

В общем вот так всё работает:
    interface IWorker
    {
        Task GetWorker();
    }

    class Worker: IWorker
    {
        readonly string _name;
        readonly int _delay;

        public Worker(string name, int delay)
        {
            _name = name;
            _delay = delay;

            TimeEngine.Add(this);
        }

        public Task GetWorker()
        {
            Task worker = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Worker {0} start", _name);

                Thread.Sleep(_delay);

                Console.WriteLine("Worker {0} end", _name);
            });

            return worker;
        }
    }

    class TimeEngine
    {
        static List<IWorker> _workers = new List<IWorker>();
        static CancellationTokenSource _cts = new CancellationTokenSource();

        public static void Add(IWorker worker)
        {
            _workers.Add(worker);
        }

        private static async Task Worker(CancellationToken ct)
        {
            while (!ct.IsCancellationRequested)
            {
                var tasks = Enumerable.Range(0, _workers.Count).Select(x => _workers[x].GetWorker());
                await Task.WhenAll(tasks);
                Console.WriteLine();
            }
        }

        public static void Start()
        {
            Worker(_cts.Token);
        }

        public static void Stop()
        {
            _cts.Cancel();
        }
    }

    class Program
    {
        readonly static int _taskCount = 10;
        readonly static Random _rnd = new Random();

        static void Main(string[] args)
        {
            for (int i = 0; i < _taskCount; i++)
            {
                new Worker(i.ToString(), _rnd.Next(500, 1500));
            }

            Console.WriteLine("TimeEngine start (Press any key to Stop)");
            TimeEngine.Start();

            Console.ReadKey();

            TimeEngine.Stop();
            Console.WriteLine("TimeEngine Stop");

            Console.ReadKey();
        }
    }
:)
Отредактировано 18.03.2015 21:54 Cynic . Предыдущая версия . Еще …
Отредактировано 18.03.2015 21:51 Cynic . Предыдущая версия .
Отредактировано 18.03.2015 21:50 Cynic . Предыдущая версия .
Re[5]: Красивое решение для задачи с потоками
От: Sinix  
Дата: 19.03.15 06:04
Оценка: +1
Здравствуйте, Cynic, Вы писали:


C>Тут я не подумал конечно, заменил на Thread.Sleep. Асинхронным метод делать не хочу, его задача возвращать таски. Мои таски не должны возвращать значение (void), т.к. предполагается что они изменяют поля класса в котором они созданы.


Обычно все методы, возвращающие Task именно что асинхронные и возвращают hot task (уже работающую задачу). Проверка аргументов и подготовка запуска делаются конечно синхронно, а вот остальная часть обычно выполняется в фоне.
Thread.Sleep и прочие ожидающие блокировки плохо сказываются на масштабируемости тасков. Если иначе никак — можно оставить, но пометить как проблемное место. (Я обычно такие места размечаю как // TOFIX: comment )


C>Вы имеете ввиду, что по тому как я использовал это метод в Main он должен быть асинхронным? Но он же работает, что плохого в такой реализации?

То, что у вас нет способа дождаться завершения операции и узнать о её результате (exception — тоже результат). В итоге приложение будет или внезапно падать, или молча проглатывать исключения (зависит от версии фреймворка и <ThrowUnobservedTaskExceptions/> (детали).

Навскидку:
http://theburningmonk.com/2012/10/c-beware-of-async-void-in-your-code/
https://msdn.microsoft.com/en-us/magazine/jj991977.aspx
http://haacked.com/archive/2014/11/11/async-void-methods/
http://www.jaylee.org/post/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.aspx
http://www.silverlightplayground.org/post/2013/02/19/The-“evil”-in-using-“async-void”.aspx

Но это относится не только к async void, речь про любой метод, который запускает задачу и не получает её значение сам (или не отдаёт таск наружу, чтобы с ней возился вызывающий).
Разумеется, есть исключения (async event handler — одно из них), в общем случае так делать не надо.


C>Кстати в вашем примере я заметил, что вы объявляете метод как

static async Task Run(CancellationToken ct)

C>... но при этом в самом методе Run нет ни одного return Так и должно быть?
Да, если возвращается Task, а не Task<T>, компилятор сам всё разрулит.d>



C>В общем вот так всё работает:

Угу. Единственно, мне очень не нравится
TimeEngine.Add(this);

в конструкторе.

В общем случае это очень, очень, очень плохая практика. Код снаружи получает доступ к объекту до того, как отработают все конструкторы (не забываем про наследников),
    static void Do(A a) { a.DoSmth(); }

    class A
    {
        public A()
        {
            Do(this);
        }

        public virtual void DoSmth()
        {
        }
    }
    class B : A 
    {
        private readonly string message;

        public B()
        {
            this.message = "Hello";
        }
        public override void DoSmth()
        {
            base.DoSmth();

            Console.WriteLine("Message: {0}, length: {1}", message, message.Length);
        }
    }

    public static void Main(string[] args)
    {
        var x = new B();
        Console.WriteLine("{0,5}",2);
    }


обычно побочные эффекты выносят из конструктора, т.е. код будет выглядеть как-то так:
            for (int i = 0; i < _taskCount; i++)
            {
                TimeEngine.Run(new Worker(i.ToString(), _rnd.Next(500, 1500));
            }
Re[6]: Красивое решение для задачи с потоками
От: Cynic Россия  
Дата: 19.03.15 10:00
Оценка:
Здравствуйте, Sinix, Вы писали:

В общем спасибо, почитаю я ещё
:)
Re[6]: Красивое решение для задачи с потоками
От: Cynic Россия  
Дата: 19.03.15 16:01
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Обычно все методы, возвращающие Task именно что асинхронные и возвращают hot task (уже работающую задачу). Проверка аргументов и подготовка запуска делаются конечно синхронно, а вот остальная часть обычно выполняется в фоне.


Короче понял я в чём проблема. Во всех примерах которые я видел обычно используется dotNET'овский метод который уже асинхронный (например Task.Delay), а в моём случае нужно создавать класс с нуля. И почему то ни в одной статье которую я читал, явного примера на этот счёт не было. Зато студия подсказала — если метод async, то либо просто await, либо await Task.Run() в теле метода должны быть. В общем вот так вот правильно:
        public async Task GetWorker()
        {
            await Task.Run(() =>
            {
                Console.WriteLine("Worker {0} start", _name);

                Thread.Sleep(_delay);       // Это только для демонстрации того как превратить синхронный код в асинхронный!

                Console.WriteLine("Worker {0} end", _name);
            });
        }
:)
Отредактировано 19.03.2015 16:14 Cynic . Предыдущая версия .
Re[7]: Красивое решение для задачи с потоками
От: Sinix  
Дата: 19.03.15 16:37
Оценка: +1
Здравствуйте, Cynic, Вы писали:


C> В общем вот так вот правильно:

В конкретно этом случае достаточно return Task.Run(), async/await тут не нужен.
Re[8]: Красивое решение для задачи с потоками
От: Cynic Россия  
Дата: 19.03.15 21:42
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, Cynic, Вы писали:



C>> В общем вот так вот правильно:

S>В конкретно этом случае достаточно return Task.Run(), async/await тут не нужен.

Да почему блин
:)
Отредактировано 19.03.2015 21:43 Cynic . Предыдущая версия .
Re[9]: Красивое решение для задачи с потоками
От: Sinix  
Дата: 20.03.15 05:46
Оценка: +1
Здравствуйте, Cynic, Вы писали:


S>>В конкретно этом случае достаточно return Task.Run(), async/await тут не нужен.

C>Да почему блин

А потому что ты сразу изучаешь и async/await, и таски. Они у тебя как одно целое воспринимаются, всё только запутывается. Сорри за диагноз по фотографии, легко могу ошибаться
Мне легко было, когда изучал — await ещё не было. А вот сколько помогал другим разобраться, всегда начинали с разделения мух и котлет.

твой код — это сокращённый способ записать вот это:
        public async Task GetWorker()
        {
            var task = Task.Run(() =>    // (1)
            {
                Console.WriteLine("Worker {0} start", _name);

                Thread.Sleep(_delay);       // Это только для демонстрации того как превратить синхронный код в асинхронный!

                Console.WriteLine("Worker {0} end", _name);
            });

            await task;                // (2)
        }


В (1) ты запускаешь задачу на выполнение (тело задачи выполняется асинхронно). В (2) — после завершения задачи сразу выходишь из метода.

Получается, код эквивалентен return Task.Run(...).
Re[10]: Красивое решение для задачи с потоками
От: Cynic Россия  
Дата: 20.03.15 08:41
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Получается, код эквивалентен return Task.Run(...).


Вы правы, наверное я действительно что-то путаю. В общем решил вот так продолжить — http://rsdn.ru/forum/dotnet/5988673.1
Автор: Cynic
Дата: 20.03.15

Спасибо
:)
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.