Здравствуйте, mapnik, Вы писали:
M>Пока космические корабли с софтом на java бороздят просторы вселенной и Марса как минимум 10 лет, MS внедряет инновационные "таски" и async/await. Видимо с помощью "программистов шарепоинт".
У btn1 появился конкурент
Это тематический раздел. Для посраться и показать себя красивого есть КСВ и мусорные разделы. Если не можете общаться без набросов — проходите мимо пожалуйста.
M>Да вы что? И что теперь в тематических разделах на вопросы по синхронизации потоков модно отвечать — "а иди как ты учи TPL", особенно когда человек пишет "Производительность решения очень важна!".
Я ещё раз попытаюсь, ок?
1. Вы не знаете про таски вообще ничего. Иначе не писали бы про производительность, инновационность тасков и про мёртвую технологию. Все три утверждения — мимо.
2. Вам топик интересен именно на предмет посраться. Вот это
что там еще у MS инновационного?
...
космические корабли с софтом на java бороздят просторы вселенной и Марса как минимум 10 лет, MS внедряет инновационные "таски" и async/await. Видимо с помощью "программистов шарепоинт"
не я писал.
3. TPL тут именно что правильное решение. Изучить недолго, всяко быстрее, чем изобрести свой велосипед с похожими характеристиками.
Если есть аргументированные возражения — велкам. Если нет — я пас.
Здравствуйте, Cynic, Вы писали:
C>Не устраивают они меня потому, что пока нужно только три "вычислительных" потока с этим можно жить, а представьте во что это превратиться если их будет пару десятков. C>Поэтому решил спросить Вас, есть ли способ более красиво решить требуемую задачу?
Здравствуйте, Sinix, Вы писали:
S>Таски были "инновационными" в 2007м, если чо.
Вот это новость!
Пока космические корабли с софтом на java бороздят просторы вселенной и Марса как минимум 10 лет, MS внедряет инновационные "таски" и async/await. Видимо с помощью "программистов шарепоинт".
Здравствуйте, Sinix, Вы писали:
S>Это тематический раздел. Для посраться и показать себя красивого есть КСВ и мусорные разделы. Если не можете общаться без набросов — проходите мимо пожалуйста.
Да вы что? И что теперь в тематических разделах на вопросы по синхронизации потоков модно отвечать — "а иди как ты учи TPL", особенно когда человек пишет "Производительность решения очень важна!".
Я бы за такой ответ сразу банил, если совсем уже грубо
Если коротко, то
1. Задачи не требуют переключения контекста.
2. Используются неблокирующие ожидания (например, на IOCP). Если задача ждёт чего-то, то она не блокирует поток. Пул может запустить следующую задачу — время CPU используется гораздо эффективней.
3. Начиная с 4.5 таски здорово оптимизированы под код в CPS-style (читай, с использованием await).
Разумеется, таски НЕ улучшат производительность, если весь ваш код утыкается в любое другое горлышко: IO, CPU, сеть, свои взаимоблокировки и тд и тп. Кроме того, добавление долгих задач может испортить общую картину из-за исчерпания пула (чинится указанием TaskCreationOption.LongRunning). Потому что
Но с самым популярным — блокировками на ожидание и неправильным использованием потоков и примитивов синхронизации — таски борятся довольно эффективно.
C>Стоит ли надеяться, что можно достигнуть большей производительности другим способом?
Разумеется. Делаете свой пул нативных потоков, поверх него изобретаете зелёные потоки или таски, добавляете load balancing + work stealing и в довершение переводите всю эту радость на lock-free, чтобы избежать лишних блокировок
Вполне возможно, есть чуть более быстрые или более удобные (для какой-то частной задачи) реализации того же самого, но общая идея не меняется.
Здравствуйте, Pzz, Вы писали:
Pzz>Ну, на вскидку:
Рабочий поток будет вечно круги нарезать.
Рабочий поток может начать работать до того как получит задание.
итп
Можно обойтись одним барьером:
Barrier barrier = new Barrier(3 + 1);//3 рабочих потока плюс 1 управляющий
...
while (true)
{
раздать_задания();
barrier.SignalAndWait();//запускает рабочие потоки
barrier.SignalAndWait();//ждет, когда рабочие потоки закончат работу
}
...
while (true)
{
barrier.SignalAndWait();//ждёт задание
сделать_работу();
barrier.SignalAndWait();//запускает получение нового задания
}
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, Cynic, Вы писали: C>Требуется найти "красивое" решение для следующей задачи. C>Есть четыре потока: один "управляющий", и три "вычислительных". Задача "управляющего потока" раздавать задачи "вычислительным потокам", а задача "вычислительных" выполнять эти задачи. При этом "вычислительные потоки" должны работать по следующему алгоритму:
Таски наше всё. Пишется за 5 минут, работает с первого раза. UPD: НЕ работает с первого раза, см ниже.
код
enum State
{
Idle, GetResult, Process
}
static volatile State state;
static async Task<int> GetNextResult(int i)
{
Debug.Assert(state == State.GetResult, "Fail");
Console.WriteLine("Geting result #{0}", i);
var delayMs = (i % 5) * 200;
await Task.Delay(TimeSpan.FromMilliseconds(delayMs));
Debug.Assert(state == State.GetResult, "Fail");
Console.WriteLine("Result #{0} ready, moving on", i);
return i;
}
static async Task<int> Process(int worker, int i)
{
Debug.Assert(state == State.Process, "Fail");
Console.WriteLine("Worker{0}: processing result #{1}", worker, i);
var delayMs = (i % 7) * 150;
await Task.Delay(TimeSpan.FromMilliseconds(delayMs));
Debug.Assert(state == State.Process, "Fail");
Console.WriteLine("Worker{0}: result #{1} processed", worker, i);
return i;
}
static async Task Run(CancellationToken ct)
{
int num = 0;
int taskCount = 3;
while (!ct.IsCancellationRequested)
{
state = State.GetResult;
var tasks = Enumerable.Range(0, taskCount).Select(i => GetNextResult(num + i));
await Task.WhenAll(tasks);
state = State.Process;
tasks = Enumerable.Range(0, taskCount).Select(i => Process(i, num + i));
await Task.WhenAll(tasks);
state = State.Idle;
num += taskCount;
}
}
static void Main()
{
Console.WriteLine("Starting, any key to exit");
var cts = new CancellationTokenSource();
var task = Run(cts.Token);
Console.ReadKey();
cts.Cancel();
task.Wait();
}
Это на случай, если получение-обработку надо разделить. Если не надо, всё тоже несложно — producer-consumer и ConcurrentQueue.
UPD: Позорище и ещё раз позорище
Вот это
while (!ct.IsCancellationRequested)
{
state = State.GetResult;
var tasks = Enumerable.Range(0, taskCount).Select(i => GetNextResult(num + i)); // (1)
await Task.WhenAll(tasks);
state = State.Process;
tasks = Enumerable.Range(0, taskCount).Select(i => Process(i, num + i)); // (2)
await Task.WhenAll(tasks);
state = State.Idle;
num += taskCount;
}
надо заменить на
while (!ct.IsCancellationRequested)
{
state = State.GetResult;
var tasks = Enumerable.Range(0, taskCount).Select(i => GetNextResult(num + i)).ToArray(); // (1)
await Task.WhenAll(tasks);
state = State.Process;
var tasks2 = tasks.Select((t, i) => Process(i, t.Result)).ToArray(); // (2)
await Task.WhenAll(tasks2);
num += taskCount;
state = State.Idle;
}
С (2) всё понятно — надо обрабатывать результат, а не "Enumerable.Range(0, taskCount)".
С (1) всё чуть сложнее. Кто сообразит, почему там нужен .ToArray() (подчеркнул) — тому пряник.
UPD2:
Ну и правильный вариант использования "Task.WhenAll()", чтобы закрыть тему.
while (!ct.IsCancellationRequested)
{
state = State.GetResult;
var resultTasks = Enumerable.Range(0, taskCount).Select(i => GetNextResult(num + i));
var results = await Task.WhenAll(resultTasks);
state = State.Process;
var processingTasks = results.Select((result, ix) => Process(ix, result)).ToArray();
await Task.WhenAll(processingTasks);
num += taskCount;
state = State.Idle;
}
Здравствуйте, G_a_r_r_y, Вы писали:
AVK>>Там есть Task.WhenAll, красивости ничего принципиально не меняют. G__>Одно из исходных требований: "Управляющий поток должен дожидаться пока все вычислительные потоки не встанут на паузу и только после этого раздавать им задачи". Поэтому в данном примере нельзя использовать Task.WhenAll
Можно. Потому что там именно логически нужно дождаться, для симуляции, а не физически поток CPU лочить.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, Cynic, Вы писали:
C>Поэтому решил спросить Вас, есть ли способ более красиво решить требуемую задачу?
1) Самый правильный способ — забить вообще про игры с потоками и просто использовать TPL. Потому что очень похоже что ты изобретаешь плохую и тормозную его версию.
2) Если уж хочется руками, то выкинуть примитивы ОС — они медленные, могут дать провал перформанса на 1-2 порядка. Вместо этого использовать lock, ReaderWriterLockSlim и Monitor.
3) Использовать параллельные коллекции типа ConcurrentQueue — большая часть операций с ними реализована lock free, это может еще до 10-20% прибытку в перформансе дать.
4) А дальше чисто алгоритмыческие фишки типа work stealing или локализации областей совместно изменяемых данных, но про них без подробного описания твоей задачи говорить бессмысленно.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, mapnik, Вы писали:
M>Отличный совет! А где же совет учить Windows Longhorn, ObjectSpaces и WinFS. И что там еще у MS инновационного?
С разморозкой
C>Поэтому решил спросить Вас, есть ли способ более красиво решить требуемую задачу?
Есть, можно сказать, уже классическая статья Joe Albahary Threading in C#. Там множество наглядных примеров, в том числе и на аналогичные задачи, если я правильно помню.
Требуется найти "красивое" решение для следующей задачи.
Есть четыре потока: один "управляющий", и три "вычислительных". Задача "управляющего потока" раздавать задачи "вычислительным потокам", а задача "вычислительных" выполнять эти задачи. При этом "вычислительные потоки" должны работать по следующему алгоритму:
Стоит на паузе в ожидании задачи
Получает очередную задачу
Получает команду на старт
Выполняет вычисления
Становится на паузу в ожидании следующей задачи (возврат к первому пункту списка)
Кроме того:
Все три потока должны начинать работу "одновременно" (это ограничение само появляется из следующего требования)
Каждый поток должен дожидаться пока остальные два потока не выполнят работу
Управляющий поток должен дожидаться пока все вычислительные потоки не встанут на паузу и только после этого раздавать им задачи
Каждый поток должен быть создан только один раз
Производительность решения очень важна!
Пытаясь решить эту задачу я написал следующий тестовый пример:
И всё вроде работает, но есть две вещи которые меня не устраивают:
Наличие большого количества экземпляров AutoResetEvent
Необходимость выполнять проверку состояния потока через ThreadState
Не устраивают они меня потому, что пока нужно только три "вычислительных" потока с этим можно жить, а представьте во что это превратиться если их будет пару десятков.
Поэтому решил спросить Вас, есть ли способ более красиво решить требуемую задачу?
Здравствуйте, Cynic, Вы писали:
C>По поводу TPL: Правильно ли я понимаю, что TPL реализует паттерн поставщик-потребитель, т.е. создаёт один или несколько рабочих потоков и скармливает им задачи.
Дефолтный шедулер внутри — примерно так, только очень грубо. Но это не должно тебя особо волновать. Тебе нужно разделить свою задачу на кусочки и продекларировать между ними зависимости. С остальным TPL разберется сам.
C> При это сами потоки не создаются и уничтожаются каждый раз для новой задачи?
Дефолтный шедулер берет потоки из стандартного пула. Не переживай за такие вещи — TPL в плане перформанса оттюнен очень качественно.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, Cynic, Вы писали:
C>В общем попробовал я таски и офигел. Ни каким другим из предложенных способов даже рядом не удавалось достигнуть той-же эффективности. Производительность других решений на 2-3 порядка ниже.
2-3 порядка, это потому что вместо спинлоков ты примитивы ОС используешь, а тела методов у тебя игрушечные. Кстати, вместо рукопашных циклом можно использовать Thread.SpinWait().
C> Каким образом удалось достигнуть такой производительности?
В данном случае спинлоки прежде всего.
C> Стоит ли надеяться, что можно достигнуть большей производительности другим способом?
Вопрос непонятный.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Тут я не подумал конечно, заменил на Thread.Sleep. Асинхронным метод делать не хочу, его задача возвращать таски. Мои таски не должны возвращать значение (void), т.к. предполагается что они изменяют поля класса в котором они созданы.
S>Если метод асинхронный — он должен возвращать Task, чтобы было его дожидаться, например S>
C>Тут я не подумал конечно, заменил на Thread.Sleep. Асинхронным метод делать не хочу, его задача возвращать таски. Мои таски не должны возвращать значение (void), т.к. предполагается что они изменяют поля класса в котором они созданы.
Обычно все методы, возвращающие Task именно что асинхронные и возвращают hot task (уже работающую задачу). Проверка аргументов и подготовка запуска делаются конечно синхронно, а вот остальная часть обычно выполняется в фоне.
Thread.Sleep и прочие ожидающие блокировки плохо сказываются на масштабируемости тасков. Если иначе никак — можно оставить, но пометить как проблемное место. (Я обычно такие места размечаю как // TOFIX: comment )
C>Вы имеете ввиду, что по тому как я использовал это метод в Main он должен быть асинхронным? Но он же работает, что плохого в такой реализации?
То, что у вас нет способа дождаться завершения операции и узнать о её результате (exception — тоже результат). В итоге приложение будет или внезапно падать, или молча проглатывать исключения (зависит от версии фреймворка и <ThrowUnobservedTaskExceptions/> (детали).
Но это относится не только к 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));
}
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(...).
Здравствуйте, Cynic, Вы писали:
C>Требуется найти "красивое" решение для следующей задачи.
Ну, на вскидку:
1. Заводим 2 объекта синхронизации. Один — системы ReaderWriterLock, другой — системы AutoResetEvent
2. Управлающий поток крутится в цикле:
while (true)
{
rwlock.AcquireWriterLock();
раздать_задания():
rwlock.ReleaseWriterLock();
event.WaitOne()
}
3. Вычислительные потоки крутятся в цикле:
while (true)
{
rwlock.AcquireReaderLock();
сделать_работу();
event.Set();
rwlock.ReleaseReaderLock()
}
Здравствуйте, Cynic, Вы писали:
C>Требуется найти "красивое" решение для следующей задачи. C>Есть четыре потока: один "управляющий", и три "вычислительных". Задача "управляющего потока" раздавать задачи "вычислительным потокам", а задача "вычислительных" выполнять эти задачи. При этом "вычислительные потоки" должны работать по следующему алгоритму: C>
C> Стоит на паузе в ожидании задачи C> Получает очередную задачу C> Получает команду на старт C> Выполняет вычисления C> Становится на паузу в ожидании следующей задачи (возврат к первому пункту списка) C>C>Кроме того: C>
C> Все три потока должны начинать работу "одновременно" (это ограничение само появляется из следующего требования) C> Каждый поток должен дожидаться пока остальные два потока не выполнят работу C> Управляющий поток должен дожидаться пока все вычислительные потоки не встанут на паузу и только после этого раздавать им задачи C> Каждый поток должен быть создан только один раз C> Производительность решения очень важна! C>
Если .net >= 4.0, то:
1) Я бы посмотрел на блокирующую очередь для ожидания новой
задачи;
2) Для синхронизации ("дожидания") выполнения потоков барьер уже посоветовали выше.
Здравствуйте, AndrewVK, Вы писали: AVK>2) Если уж хочется руками, то выкинуть примитивы ОС — они медленные, могут дать провал перформанса на 1-2 порядка. Вместо этого использовать lock, ReaderWriterLockSlim и Monitor.
Я конечно извиняюсь, скорее всего я туплю, но как можно использовать например ReaderWriterLockSlim для моей задачи
Мне нужно, чтобы например три рабочих потока взяли себе по задаче выполнили их и при этом каждый из них дождался пока закончат остальные, потом все снова взяли себе по задаче и т.д. lock() (ну и Monitor) я ещё как то смог прикрутить к этой задаче, а вот про ReaderWriterLockSlim идей чего то нету. Можно примерчик
Здравствуйте, nikov, Вы писали:
N>Советую забыть про потоки и учить таски и TPL.
По поводу TPL: Правильно ли я понимаю, что TPL реализует паттерн поставщик-потребитель, т.е. создаёт один или несколько рабочих потоков и скармливает им задачи. При это сами потоки не создаются и уничтожаются каждый раз для новой задачи? Вопрос такой возник, потому что важна производительность решения.
Здравствуйте, Cynic, Вы писали:
C>По поводу TPL: Правильно ли я понимаю, что TPL реализует паттерн поставщик-потребитель, т.е. создаёт один или несколько рабочих потоков и скармливает им задачи. При этом сами потоки не создаются и уничтожаются каждый раз для новой задачи? Вопрос такой возник, потому что важна производительность решения.
Не совсем так. Скорее, таски — это довольно толковая реализация пары work item + шедулер.
Шадулер может быть любой, по умолчанию используется крайне эффективный пул задач. Плюс с тасками и await очень удобно писать код с non-blocking wait. Вот тут
Здравствуйте, Cynic, Вы писали:
C>Я конечно извиняюсь, скорее всего я туплю, но как можно использовать например ReaderWriterLockSlim для моей задачи
RWLock вместо полной блокировки нужен при обращении к обычным дотнетовским коллекциям — они поддерживают режим с параллельным чтением без блокировки.
C>Мне нужно, чтобы например три рабочих потока взяли себе по задаче выполнили их и при этом каждый из них дождался пока закончат остальные, потом все снова взяли себе по задаче и т.д.
Типичная для TPL задача. Зачем велосипед городить неясно.
И, кстати, это не задача, а твое решение уже. Было бы неплохо исходную задачу описать, зачем тебе потоки понадобились и именно 3, и почему они должны ждать завершения. Координация то исходно, наверное, не по потокам, а по результатам вычислений, верно?
C> lock() (ну и Monitor) я ещё как то смог прикрутить к этой задаче, а вот про ReaderWriterLockSlim идей чего то нету. Можно примерчик
У тебя же в реальном коде какие то данные появятся, а не просто пустые потоки крутиться будут, верно?
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, Sinix, Вы писали:
S>Шадулер может быть любой, по умолчанию используется крайне эффективный пул задач. Плюс с тасками и await очень удобно писать код с non-blocking wait. Вот тут
был пример, можете попробовать достичь того же без тасков.
В общем попробовал я таски и офигел. Ни каким другим из предложенных способов даже рядом не удавалось достигнуть той-же эффективности. Производительность других решений на 2-3 порядка ниже. Тестировал примерно так:
class Program
{
static readonly int counterValue = 1000000;
static readonly int taskCount = 30000;
static void Main(string[] args)
{
List<Action> actions = new List<Action>();
for (int i = 0; i < taskCount; i++)
{
actions.Add(new Action(Work));
}
Console.WriteLine("Task speed test");
DateTime start = DateTime.Now;
Parallel.Invoke(actions.ToArray());
DateTime stop = DateTime.Now;
Console.WriteLine("Time is: {0} secs", (stop - start).TotalSeconds);
Console.ReadLine();
}
public static void Work()
{
int counter = counterValue;
while (true)
{
counter--;
if (counter <= 0)
return;
}
}
}
Появилось несколько вопросов: Каким образом удалось достигнуть такой производительности?
Стоит ли надеяться, что можно достигнуть большей производительности другим способом?
Интересно что если менять параметры counterValue и taskCount увеличивая в 10 раз один и в 10 раз уменьшая другой (или наоборот), то время выполнения задач практически не меняется. На моей машине всегда примерно 9 секунд. Складывается впечатление, что создается ограниченное количество потоков которым потом по очереди скармливаются задачи. В общем как это работает?
Здравствуйте, breee breee, Вы писали:
N>>Советую забыть про потоки и учить таски и TPL. BB>А какую-нибудь толковую книгу (или серию статей) по ним посоветуете?
Присоединяюсь к вопросу.
Материалов мало, полтора года назад всё ограничивалось вот этим
Здравствуйте, AndrewVK, Вы писали:
AVK> Кстати, вместо рукопашных циклом можно использовать Thread.SpinWait().
Не понимаю где мене тут это может помочь.
C>> Стоит ли надеяться, что можно достигнуть большей производительности другим способом?
AVK>Вопрос непонятный.
Можно ли добиться большей производительности другим способом? Или выигрыш будет не значительным и не стоит заморачиваться?
Здравствуйте, Cynic, Вы писали:
AVK>> Кстати, вместо рукопашных циклом можно использовать Thread.SpinWait(). C>Не понимаю где мене тут это может помочь.
int counter = counterValue;
while (true)
{
counter--;
if (counter <= 0)
return;
}
Вместо этого можно просто Thread.SpinWait(counterValue).
C>>> Стоит ли надеяться, что можно достигнуть большей производительности другим способом? AVK>>Вопрос непонятный. C>Можно ли добиться большей производительности другим способом? Или выигрыш будет не значительным и не стоит заморачиваться?
Не стоит заморачиваться.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, G_a_r_r_y, Вы писали:
G__>Ничего особенного, но может кому-то будет интересно.
Не нравится мне эта синхронизация из костылей и спичек (аля "while (_active)" и "_actionsCount — i == _doneCount"). Одна опечатка и упс. Собственно, она у вас уже есть, поля надо бы volatile пометить
Вариант с тасками гораздо проще. Только Task.WaitAll() лучше заменить на await, ждущая блокировка тут не нужна. И "new Task() ... Start()" — на "Task.Run()";
Более-менее правильный пример выше
Здравствуйте, Sinix, Вы писали:
S>Не нравится мне эта синхронизация из костылей и спичек (аля "while (_active)" и "_actionsCount — i == _doneCount"). Одна опечатка и упс.
Никаких костылей и спичек, хотя выглядит сложновато, но можно и упростить, сразу не сообразил.
S>Вариант с тасками гораздо проще. Только Task.WaitAll() лучше заменить на await, ждущая блокировка тут не нужна. И "new Task() ... Start()" — на "Task.Run()";
У меня пример для 4 фреймворка (посыпаю голову пеплом, забыл предупредить), там этих "красивостей" еще нет.
А ждущая блокировка нужна по условиям задачи.
S>Более-менее правильный пример выше
Здравствуйте, AndrewVK, Вы писали:
AVK>Там есть Task.WhenAll, красивости ничего принципиально не меняют.
Одно из исходных требований: "Управляющий поток должен дожидаться пока все вычислительные потоки не встанут на паузу и только после этого раздавать им задачи". Поэтому в данном примере нельзя использовать Task.WhenAll, а нужна именно блокировка Task.WaitAll управляющего потока.
Здравствуйте, Cynic, Вы писали:
C>Попробовал сделать как у Вас:
Не, это совсем не как у меня
Я завтра с утра напишу подробней, коротко: методы Task.Delay(), Task.Start() etc никак не влияют на текущий поток, они запускают новую задачу. Чтобы получить результат задачи — или task.Wait() / task.Result (блокирующее ожидание), или await.
Здравствуйте, 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);
});
}