1) Почему balance не будет биться ( != 0), если в коде Add\Remove заменить Task на void?
Использую корректную версия кода, где вместо Factory использую Task.Run.
2)Почему SemaphoreSlim не бросает исключение, ведь захватить его может один поток, а освободить другой(continuation)?
Бегло посмотрел по исходникам, не похоже, чтобы runtime c контекстами возился и т.п.
.
S>1) Почему balance не будет биться ( != 0), если в коде Add\Remove заменить Task на void? S>Использую корректную версия кода, где вместо Factory использую Task.Run.
... S>async void -- какая-то инфернальная конструкция.
Потому что async void никто не ждёт и про особенности таких методов куча информации в интернетах.
WhenAll устроит тот факт, что задачи дошли до своего первого await, т.е. эффект по сути тот же, что был в исходном вопросе
S>2)Почему SemaphoreSlim не бросает исключение, ведь захватить его может один поток, а освободить другой(continuation)? S>Бегло посмотрел по исходникам, не похоже, чтобы runtime c контекстами возился и т.п.
потому что такое поведение у класса задокументировано
The SemaphoreSlim class doesn't enforce thread or task identity on calls to the Wait, WaitAsync, and Release methods. In addition, if the SemaphoreSlim(Int32) constructor is used to instantiate the SemaphoreSlim object, the CurrentCount property can increase beyond the value set by the constructor. It is the programmer's responsibility to ensure that calls to Wait or WaitAsync methods are appropriately paired with calls to Release methods.
Очевидно, что async void-методы имеют ряд недостатков по сравнению с async Task-методами, но они весьма полезны в одном конкретном случае: использовании в качестве асинхронных обработчиков событий. Различия в семантике имеют смысл для таких обработчиков. Они генерируют свои исключения непосредственно в SynchronizationContext, т. е. ведут себя так же, как синхронные обработчики событий. Синхронные обработчики обычно являются закрытыми, поэтому они не подлежат композиции и их нельзя тестировать напрямую. Я предпочитаю такой подход: минимизация кода в асинхронных обработчиках событий, например пусть он ждет на async Task-методе, содержащем реальную логику
Async void-методы могут посеять хаос, если вызвавший код не ожидает, что они окажутся асинхронными. Когда возвращается тип Task, вызвавший код знает, что имеет дело с операцией future, а когда возвращаемый тип — void, вызвавший код может предположить, что метод завершается к моменту возврата им управления. У этой проблемы может быть множество неожиданных проявлений. Обычно неправильно предоставлять async-реализацию (или переопределение) void-метода какого-либо интерфейса (или базового класса). Некоторые события также предполагают, что их обработчики завершаются, когда они возвращают управление. Еще одна тонкая ловушка — передача асинхронной лямбды в метод, принимающий параметр Action; в этом случае async-лямбда возвращает void и наследует все проблемы async void-методов. Как общее правило, async-лямбды следует использовать, только если они преобразуются в тип делегата, который возвращает Task (например, Func<Task>).
Ну и как в той ветке проверить обшее количество выполненных add remove
и солнце б утром не вставало, когда бы не было меня
. S>>1) Почему balance не будет биться ( != 0), если в коде Add\Remove заменить Task на void? S>>Использую корректную версия кода, где вместо Factory использую Task.Run. S>>async void -- какая-то инфернальная конструкция.
K>Потому что async void никто не ждёт и про особенности таких методов куча информации в интернетах.
Я и написал, что это инфернальная конструкция, которую не советуют использовать от слова совсем.
K>WhenAll устроит тот факт, что задачи дошли до своего первого await, т.е. эффект по сути тот же, что был в исходном вопросе
Почему компилятор не делает никакой разницы между Add/Remove возращающих Task и void, если поведение будет разным?
Даже warning'ов нету.
Здравствуйте, Sharov, Вы писали:
S>Почему компилятор не делает никакой разницы между Add/Remove возращающих Task и void, если поведение будет разным? S>Даже warning'ов нету.
S>Почему компилятор не делает никакой разницы между Add/Remove возращающих Task и void, если поведение будет разным? S>Даже warning'ов нету.
имеется в виду TaskFactory.StartNew ?
там где void будет Task t = TaskFactory.StartNew(Action)
где Task — Task<Task> t = TaskFactory.StartNew(Func<Task>)
с какой стати тут warning
Das Reich der Freiheit beginnt da, wo die Arbeit aufhört. (c) Karl Marx
.
S>1) Почему balance не будет биться ( != 0), если в коде Add\Remove заменить Task на void? S>Использую корректную версия кода, где вместо Factory использую Task.Run. S>
S>async void -- какая-то инфернальная конструкция.
S>2)Почему SemaphoreSlim не бросает исключение, ведь захватить его может один поток, а освободить другой(continuation)? S>Бегло посмотрел по исходникам,
не похоже, чтобы runtime c контекстами возился и т.п.
А зачем использовать Task.Run?
ведь проще добавить
tasks.Add(add());
tasks.Add(remove());
ну можно еще и ConfigureAwait длбавить
только вот
async void Add() не возвращает Task
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, ksg71, Вы писали:
K>Здравствуйте, Sharov, Вы писали:
S>>Почему компилятор не делает никакой разницы между Add/Remove возращающих Task и void, если поведение будет разным? S>>Даже warning'ов нету.
K>имеется в виду TaskFactory.StartNew ? K>там где void будет Task t = TaskFactory.StartNew(Action) K> где Task — Task<Task> t = TaskFactory.StartNew(Func<Task>) K>с какой стати тут warning
Task.Run имеется в виду и в обоих случаях он вернет Task, но поведение у код будет совсем разным.
Здравствуйте, Serginio1, Вы писали:
S>А зачем использовать Task.Run?
Factory верент Task<Task>
S>ведь проще добавить S> tasks.Add(add()); S> tasks.Add(remove()); S>ну можно еще и ConfigureAwait длбавить S>только вот S>async void Add() не возвращает Task
Вот только в том дело, что при Task.Run компилятор это дело маскирует.
Здравствуйте, Sharov, Вы писали:
S>Здравствуйте, Serginio1, Вы писали:
S>>А зачем использовать Task.Run?
S>Factory верент Task<Task>
S>>ведь проще добавить S>> tasks.Add(add()); S>> tasks.Add(remove()); S>>ну можно еще и ConfigureAwait длбавить S>>только вот S>>async void Add() не возвращает Task
S>Вот только в том дело, что при Task.Run компилятор это дело маскирует.
Не то, что маскирует. Он не может понять какой метод вызвать
ПЕРЕГРУЗКИ
Run(Action)
Ставит в очередь заданную работу для запуска в пуле потоков и возвращает объект Task, представляющий эту работу.
Run(Func<Task>)
Ставит в очередь указанную работу для запуска в пуле потоков и возвращает прокси для задачи, возвращаемой функцией function.
Но единственный момент где стоит применять
tasks.Add(Task.Run(async ()=> await add());
вместо
tasks.Add(add()) это указать TaskCreationOptions.LongRunning)
и если метод начинается с долгого синхронного метода
Здравствуйте, Sharov, Вы писали:
S>Здравствуйте, Serginio1, Вы писали:
S>>Просто async void не возвращает Task и к нему невозможно применить ContinueWith
S>Task.Run и ко это успешно маскируют. Т.е. семантически разница между Func<Task> и Action есть, S>а на уровне языка нету.
Ну да перегрузка методов. А компилятор не может понять. Лучше указать самому подсказку, что и как выполнять
Нет есть разница вызова Task с await и без await
static asinc Task ShowThreadInfo(String s)
{
await Task.Yield();
Console.WriteLine("{0} Thread ID: {1}",
s, Thread.CurrentThread.ManagedThreadId);
}
var t = Task.Run(async () => await ShowThreadInfo("Task") );
var t = Task.Run(() => ShowThreadInfo("Task") );
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
S>>Вот только в том дело, что при Task.Run компилятор это дело маскирует. S> Не то, что маскирует. Он не может понять какой метод вызвать S>ПЕРЕГРУЗКИ S>Run(Action) S>Ставит в очередь заданную работу для запуска в пуле потоков и возвращает объект Task, представляющий эту работу. S>Run(Func<Task>) S>Ставит в очередь указанную работу для запуска в пуле потоков и возвращает прокси для задачи, возвращаемой функцией function.
А в чем разница между
возвращает объект Task, представляющий эту работу.
и
возвращает прокси для задачи, возвращаемой функцией function
?
Т.е. исходя из описания выше методы с async void должны давать корректный результат, что не так.
Я не понимаю, почему Task.Run в случае Action вообще что-то возвращает, почему для Action не сделать отдельный вызов?
S>Я не понимаю, почему Task.Run в случае Action вообще что-то возвращает, почему для Action не сделать отдельный вызов?
Изначально Task.Run был сделан для запуска синхронного Action. Для Action вызов то есть и возвращается Task, только если внутри Action есть await он до этого await и дойдет и будет считаться завершенной
Run(Func<Task>) по моему мнению не нужен проще метод расширение
Здравствуйте, Serginio1, Вы писали:
S>Здравствуйте, Sharov, Вы писали:
S>>Я не понимаю, почему Task.Run в случае Action вообще что-то возвращает, почему для Action не сделать отдельный вызов? S>Изначально Task.Run был сделан для запуска синхронного Action. Для Action вызов то есть и возвращается Task, только если внутри Action есть await он до этого await и дойдет и будет считаться завершенной
Точно, я совсем забыл об этом... Я всегда запускал задачи через Factory, Task.Run никогда, кроме поиграться, не использовал.
Соотв. все становится на свои места -- сделать задачей синхронный код (Action), т.е. выполнить его синхронно и вернуть
Task для этого дела (ну нужно для какого-то api). Так вот, если этот Action асинхронный, то да, будет не пойми что,
формально Task завершится после первого await, а фактически хрен знает что там будет с этой асинхронной задачей...