SemaphoreSlim(1, 1) WaitAsync 2 -- пару вопросов.
От: Sharov Россия  
Дата: 29.12.20 21:27
Оценка:
Здравствуйте.

Поигрался с примером из предыдущего вопроса
Автор: #John
Дата: 28.12.20
.

1) Почему balance не будет биться ( != 0), если в коде Add\Remove заменить Task на void?
Использую корректную версия кода, где вместо Factory использую Task.Run.
static async void Add()
       {
           await s.WaitAsync();//.WaitAsync();
           try
           {
               _balance++;
               await Task.Yield();
           }
           finally
           {
               s.Release();
           }
       }



async void -- какая-то инфернальная конструкция.

2)Почему SemaphoreSlim не бросает исключение, ведь захватить его может один поток, а освободить другой(continuation)?
Бегло посмотрел по исходникам, не похоже, чтобы runtime c контекстами возился и т.п.
Кодом людям нужно помогать!
Re: SemaphoreSlim(1, 1) WaitAsync 2 -- пару вопросов.
От: karbofos42 Россия  
Дата: 30.12.20 01:10
Оценка: 14 (1)
Здравствуйте, Sharov, Вы писали:

S>Здравствуйте.


S>Поигрался с примером из предыдущего вопроса
Автор: #John
Дата: 28.12.20
.


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 контекстами возился и т.п.

потому что такое поведение у класса задокументировано

https://docs.microsoft.com/en-us/dotnet/api/system.threading.semaphoreslim?view=netframework-4.8

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.

Re: SemaphoreSlim(1, 1) WaitAsync 2 -- пару вопросов.
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 30.12.20 08:06
Оценка: 7 (1)
Здравствуйте, Sharov, Вы писали:

Избегайте async void

Очевидно, что 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
и солнце б утром не вставало, когда бы не было меня
Отредактировано 30.12.2020 8:18 Serginio1 . Предыдущая версия . Еще …
Отредактировано 30.12.2020 8:08 Serginio1 . Предыдущая версия .
Re[2]: SemaphoreSlim(1, 1) WaitAsync 2 -- пару вопросов.
От: Sharov Россия  
Дата: 30.12.20 11:46
Оценка:
Здравствуйте, karbofos42, Вы писали:

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

S>>Здравствуйте.
S>>Поигрался с примером из предыдущего вопроса
Автор: #John
Дата: 28.12.20
.

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'ов нету.
Кодом людям нужно помогать!
Re[3]: SemaphoreSlim(1, 1) WaitAsync 2 -- пару вопросов.
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 30.12.20 12:25
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Почему компилятор не делает никакой разницы между Add/Remove возращающих Task и void, если поведение будет разным?

S>Даже warning'ов нету.

Ну можно посмотреть шаблон
WhenAllOrFirstException

Просто async void не возвращает Task и к нему невозможно применить ContinueWith

Можно попробовать
tasks.Add(Task.Run(async ()=> await remove() ));

А вот хрен, нельзя говорит cs4033 применит await к void, должен быть async
И тут же CS4008 ожидание void невозможно
и солнце б утром не вставало, когда бы не было меня
Отредактировано 30.12.2020 13:18 Serginio1 . Предыдущая версия . Еще …
Отредактировано 30.12.2020 13:11 Serginio1 . Предыдущая версия .
Отредактировано 30.12.2020 13:10 Serginio1 . Предыдущая версия .
Отредактировано 30.12.2020 12:31 Serginio1 . Предыдущая версия .
Re[3]: SemaphoreSlim(1, 1) WaitAsync 2 -- пару вопросов.
От: ksg71 Германия  
Дата: 30.12.20 14:00
Оценка:
Здравствуйте, Sharov, Вы писали:



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
Re: SemaphoreSlim(1, 1) WaitAsync 2 -- пару вопросов.
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 30.12.20 15:06
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Здравствуйте.


S>Поигрался с примером из предыдущего вопроса
Автор: #John
Дата: 28.12.20
.


S>1) Почему balance не будет биться ( != 0), если в коде Add\Remove заменить Task на void?

S>Использую корректную версия кода, где вместо Factory использую Task.Run.
S>
S>static async void Add()
S>       {
S>           await s.WaitAsync();//.WaitAsync();
S>           try
S>           {
S>               _balance++;
S>               await Task.Yield();
S>           }
S>           finally
S>           {
S>               s.Release();
S>           }
S>       }
S>



S>async void -- какая-то инфернальная конструкция.


S>2)Почему SemaphoreSlim не бросает исключение, ведь захватить его может один поток, а освободить другой(continuation)?

S>Бегло посмотрел по исходникам,
не похоже, чтобы runtime c контекстами возился и т.п.

А зачем использовать Task.Run?

ведь проще добавить
tasks.Add(add());
tasks.Add(remove());
ну можно еще и ConfigureAwait длбавить
только вот
async void Add() не возвращает Task
и солнце б утром не вставало, когда бы не было меня
Re[4]: SemaphoreSlim(1, 1) WaitAsync 2 -- пару вопросов.
От: Sharov Россия  
Дата: 30.12.20 19:53
Оценка:
Здравствуйте, Serginio1, Вы писали:

S>Просто async void не возвращает Task и к нему невозможно применить ContinueWith


Task.Run и ко это успешно маскируют. Т.е. семантически разница между Func<Task> и Action есть,
а на уровне языка нету.
Кодом людям нужно помогать!
Re[4]: SemaphoreSlim(1, 1) WaitAsync 2 -- пару вопросов.
От: Sharov Россия  
Дата: 30.12.20 19:55
Оценка:
Здравствуйте, 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, но поведение у код будет совсем разным.
Кодом людям нужно помогать!
Re[2]: SemaphoreSlim(1, 1) WaitAsync 2 -- пару вопросов.
От: Sharov Россия  
Дата: 30.12.20 19:56
Оценка:
Здравствуйте, 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 компилятор это дело маскирует.
Кодом людям нужно помогать!
Re[3]: SemaphoreSlim(1, 1) WaitAsync 2 -- пару вопросов.
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 30.12.20 20:35
Оценка: 7 (1)
Здравствуйте, 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(Task.Run(async ()=> await remove());


Но единственный момент где стоит применять
tasks.Add(Task.Run(async ()=> await add());
вместо
tasks.Add(add()) это указать TaskCreationOptions.LongRunning)

и если метод начинается с долгого синхронного метода



async Task CallLongMethod()
{
  
 LongMethod()
  await Task.Yield();
}


Но у Run таких методов нет. Нужно использовать Task.Factory.StartNew
Согласен намутили. Нужно более прозрачно
и солнце б утром не вставало, когда бы не было меня
Отредактировано 31.12.2020 9:19 Serginio1 . Предыдущая версия . Еще …
Отредактировано 30.12.2020 20:48 Serginio1 . Предыдущая версия .
Re[5]: SemaphoreSlim(1, 1) WaitAsync 2 -- пару вопросов.
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 30.12.20 20:44
Оценка:
Здравствуйте, 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") );
и солнце б утром не вставало, когда бы не было меня
Re[4]: SemaphoreSlim(1, 1) WaitAsync 2 -- пару вопросов.
От: Sharov Россия  
Дата: 31.12.20 10:16
Оценка:
Здравствуйте, 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 не сделать отдельный вызов?
Кодом людям нужно помогать!
Re[5]: SemaphoreSlim(1, 1) WaitAsync 2 -- пару вопросов.
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 31.12.20 10:59
Оценка: 7 (1)
Здравствуйте, Sharov, Вы писали:


S>Я не понимаю, почему Task.Run в случае Action вообще что-то возвращает, почему для Action не сделать отдельный вызов?

Изначально Task.Run был сделан для запуска синхронного Action. Для Action вызов то есть и возвращается Task, только если внутри Action есть await он до этого await и дойдет и будет считаться завершенной
Run(Func<Task>) по моему мнению не нужен проще метод расширение

async Task RunInOtherThread(this Func<Task> task)
{
  await Task.Yield();
  return task();
}
и солнце б утром не вставало, когда бы не было меня
Re[6]: SemaphoreSlim(1, 1) WaitAsync 2 -- пару вопросов.
От: Sharov Россия  
Дата: 31.12.20 14:10
Оценка:
Здравствуйте, 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, а фактически хрен знает что там будет с этой асинхронной задачей...
Кодом людям нужно помогать!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.