Как стать автором
Обновить

Комментарии 42

Что насчет асинхронности?
Да! Используем ее где только можно!

Аргументируйте. В том смысле, что докажите, вот этот код (ваш пример):


public void Do()
{
    var myTask = GetFederalDistrictsAsync ();
    foreach (var item in myTask.Result)
    {
         //Ваш код
    }
}

public async Task<List<FederalDistrict>> GetFederalDistrictsAsync()
{
    var conn = configurationRoot.GetConnectionString("EFCoreTestContext");
    optionsBuilder.UseSqlServer(conn);
    using var context = new EFCoreTestContext(optionsBuilder.Options);
    return await context.FederalDistricts.ToListAsync();
}

быстрее, чем вот этот:


public void Do()
{
    foreach (var item in GetFederalDistricts())
    {
         //Ваш код
    }
}

public List<FederalDistrict> GetFederalDistricts()
{
    var conn = configurationRoot.GetConnectionString("EFCoreTestContext");
    optionsBuilder.UseSqlServer(conn);
    using var context = new EFCoreTestContext(optionsBuilder.Options);
    return context.FederalDistricts.ToList();
}

Не говоря уже о том, что вот так:


var myTask = GetFederalDistrictsAsync ();
foreach (var item in myTask.Result)

делать без веских причин не надо.

Так автор и не говорит, что он быстрее.
Он говорит, что операция может быть выполнена асинхронно, и на время ожидания ответа от сервера поток будет освобожден для выполнения других задач.

Конкретно код автора потока не освободит, даже наоборот: займёт ещё один, пусть и на короткое время.

Так автор и не говорит, что он быстрее.

Заголовок статьи вроде бы "ускоряемся". Я по умолчанию предполагаю, что советы — они про ускорение.


и на время ожидания ответа от сервера поток будет освобожден для выполнения других задач.

Не в случае кода автора.


А, главное, если моя задача в том, чтобы запросы выполнялись быстрее, как мне это поможет?

А, главное, если моя задача в том, чтобы запросы выполнялись быстрее, как мне это поможет?

Я думаю автор хотел сказать, что если бы запросы действительно выполнялись асинхронно, то в целом приложение бы выиграло из-за более эффективного использования потоков в пуле.
То есть запросы это конечно не ускорит, но писать асинхронный код обычно полезно.
то в целом приложение бы выиграло из-за более эффективного использования потоков в пуле

Возможно выиграло бы. Это уже зависит от приложения, его задач и характера нагрузки.


То есть запросы это конечно не ускорит, но писать асинхронный код обычно полезно.

Мне кажется, что полезно понимать, что в этом случае происходит, и где профит, а где — потери.

Не в случае кода автора.

Про метод Do я и не спорю. Автор поспешил и, возможно, не очень хорошо разбирается в магии async/await

А, главное, если моя задача в том, чтобы запросы выполнялись быстрее, как мне это поможет?

Запрос, в общем случае выполнится медленнее, из за накладных расходов на работу с тасками.
Но в зависимости от специфики приложения, приложение в целом может начать работать быстрее.
Про метод Do я и не спорю. Автор поспешил и, возможно, не очень хорошо разбирается в магии async/await

Может быть, если не очень хорошо разбираешься в магии, не надо советовать ее применять "где только можно"?


Запрос, в общем случае выполнится медленнее, из за накладных расходов на работу с тасками.

То есть эффект достигнут прямо противоположный обещанному в заголовке.


Но в зависимости от специфики приложения, приложение в целом может начать работать быстрее.

Вот именно что "в зависимости от специфики". Это прямо противоречит "где только можно".

Код выполняется синхронно так как блокируется поток в момент вызова .Result на таске
А как было бы правильно переписать этот пример без блокировок потока?
public async Task Do()
{
    var districts = await GetFederalDistrictsAsync();
    foreach (var district in districts)
    {
    }
}


Примерно так. Асинхронность она как вирус — распространяется по всему проекту.
А что тогда на самом верхнем уровне?

Фреймворк, который умеет в асинхронию. asp.net core, например.

То есть если сильно упростить — идея в том, чтобы протащить асинхронность до, условно, пользовательского отображения, и там ее отработать без «фриза» интерфейса?

В каком-то смысле. И в обратную сторону — сделать пользовательский интерфейс асинхронным, чтобы сделать его более отзывчивым.


Все-таки, нет. Отзывчивый пользовательский интерфейс — это одна задача. Более разумное использование ресурсов (например, потоков) в серверном приложении — другая.

Спасибо! Но все равно тогда до конца не понимаю. Допустим есть серверное приложение с асинхронным API — в чем тогда будет выражаться разумное использование ресурсов? Ведь это асинхронное API будет дергать некий клиент. На уровне серверного приложения разве будет выигрыш в том, что API асинхронное, а не синхронное? Ведь если я ничего не путаю, на обработку каждого запроса синхронного API все равно выделяется отдельный поток из пула?
На уровне серверного приложения разве будет выигрыш в том, что API асинхронное, а не синхронное?

Может быть.


Ведь если я ничего не путаю, на обработку каждого запроса синхронного API все равно выделяется отдельный поток из пула?

В том-то и дело, что если API реализовано как асинхронное, и в какой-то момент в нем случается ожидание, не требующее потока (например, мы ждем HTTP-запрос от следующего сервера, или в БД полезли), мы можем отпустить поток, и он будет доступен для следующего запроса. И, возможно, этот запрос даже выполнится быстрее, чем время ожидания.


Это не всегда хорошо, но иногда это выигрыш.

А, вот оно как. Спасибо огромное за разъяснение (к сожалению не могу поставить плюс комментарию)
Что-то вроде такого.
async void button_Click(object sender, EventArgs e)
{ 
     await Do();
}
или такого
[HttpGet]
public async Task<IHttpActionResult> Get()
{
      await Do();
      return Ok();
}
Душевно благодарю на указанную неточность! Исправлено, с добавлением расширенного примера использования асинхронного запроса из репозитория бизнес-логики в компонент представления ASP .NET Core. В репозитории присуствует синхронный и асинхронный варианты методов получения данных. Еще раз спасибо, за просмотр моей статьи!
Исправлено

Неа. Рекомендация "используем асинхронность где только можно" так и осталась.


Аргументируйте.

В core вроде как вызовы .Result и .Wait() не могут приводить к дедлокам, насколько мне известно. Хотя, конечно, ничто не мешает в этом примере заавейтить результат.

Поправьте меня, если я ошибаюсь? Есть ли какие-то другие подводные камни, кроме тех что исчезли с уходом контекста синхронизации?

Насколько я могу видеть, самое важное не поменялось (выделение мое):


Accessing the property's get accessor blocks the calling thread until the asynchronous operation is complete

Ну то есть да, если коду внутри не нужно возвращаться в тот же поток, дедлока не будет. Но вот потоков может быть занято больше, что скорее плохо, чем хорошо.

Что значит — не могут? Контексты синхронизации никуда не делись, просто они в ASP.NET Core не используются.


Попробуйте написать декстопное приложение на AvaloniaUI — и дедлоки при вызовах .Result и .Wait() сразу вернутся!

Статья в принципе не особо высокого уровня, каких-то кардинально новых вещей не увидел, хотя и хотелось. (Но всё равно статье плюс, в целом сгодится хотя бы как "повторение — мать учения").


Не хватает конкретики, на сколько можно разогнаться тем или иным способом.
Например, насколько я помню, майкрософт в одном из документов приводила цифры про ускорение при помощи .AsNoTracking и там были цифры порядка 15-20% (документ 2005 — 2008 года, его уже на офсайте найти весьма проблематично), новых исследований для core я к сожалению не видел, поэтому просто полагаюсь что по-прежнему даёт довольно существенный прирост.


Также было бы неплохо вот тут проиллюстрировать хотя простым тестом:


Если вставляемые записи одинаковые имеет смысл использовать одну операцию сохранения на все записи. Если контекст транзакции сложный, то есть состоит из нескольких независимых операций, то можно выполнять сохранение после выполнения каждой операции. А еще правильней использовать асинхронное сохранение в транзакции.

Можно было бы создать таблицу на 10-15 полей без ключей к другим таблицам и показать на сколько примерно возрастёт скорость. Даже семейство графиков построить: если таблица на 5 полей, на 10, на сто. Или померять как влияют самые типовые поля типа строк или чисел.


А что вы знаете про компилированные запросы LINQ?

Да и вот тут пример с цифрами не помешал бы. Я не знаю, насколько большие цифры будут, мне кажется, что не особо большие — потому что в .net первый раз компиляция происходит довольно медленно, но за счёт того, что повторно код заново не компилируется — то на второй и последующие разы эффект уже не особо сильный. Ошибаюсь? Нет? Вот не знаю, хотелось бы видеть цифры.

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
AsQueryable
Принимает параметр: Expression<Func <TSource, bool>

Что я только что прочитал?

Это ошибка. Спасибо, что заметили. Исправлено и дополнено примером. Еще раз благодарю!
В 3 примере, про выполнение шарп кода в запросах, вы привели данный код как пример правильного:
var blogs = context.Blogs.AsEnumerable().Where(blog => StandardizeUrl(blog.Url).Contains("dotnet")).ToList();

Но не противоречит ли этот пример теме статьи? Ведь все данные все равно загрузятся в память.
Думаю, что нет. Ключевым моментом является использование AsEnumerable() так как предполагается получение отфильтрованных записей только на чтение.
Соглашусь, что StandardizeUrl(blog.Url) может быть выполнено позже когда найдены все записи по условию .Contains(«dotnet»).
Соглашусь, что StandardizeUrl(blog.Url) может быть выполнено позже когда найдены все записи по условию .Contains(«dotnet»).

А что делать с тем печальным фактом, что до Standardize Сontains находит меньше записей, чем после?

НЛО прилетело и опубликовало эту надпись здесь
Солидарен. Мало того, что с 3 версии EF Core выпилено дефолтное client-side evaluation и теперь такой код кинет ошибку в момент выполнения, если убрать AsEnumerable, а если не убирать, то сравнение строк на недавний момент существовует лишь в виде Ordinal (не проверял 3.1 ещё), так ещё и вопрос к производительности — одно дело contains, который легко может быть выполнен на сервере и само по себе более легковесная операция, нежели преобразование целого урла к какому-то виду.
Вы написали "… нежели преобразование целого урла к какому-то виду."
Урл преобразуется на стороне сервера, этот код не передается на сервер можно проверить профайлером SQL.
НЛО прилетело и опубликовало эту надпись здесь
c=>c.url.ToLower().StartsWith("http://")

Если рассматривать общий случай, то это скорее плохой код. Зависимость от регистра определяется по коллейшену столбца в MSSQL и по умолчанию они регистронезависимые а значит преобразование к нижнему регистру тут лишнее.
Ну и в целом лучше при сравнении преобразовывать к верхнему регистру потому что это быстрее.
НЛО прилетело и опубликовало эту надпись здесь
Транзакции, еще правильнее.
using(var db = new NorthwindEntities())
{

    var  obj1 = new Customer();
    obj1.CustomerID = "ABCDE";
    obj1.CompanyName = "Company 1";
    obj1.Country = "USA";

    var  obj2 = new Customer();
    obj2.CustomerID = "PQRST";
    obj2.CompanyName = "Company 2";    
    obj2.Country = "USA";

    var transaction = db.Database.BeginTransaction();
    try
    {
        db.Customers.Add(obj1); 
        db.Customers.Add(obj2);
        db.SaveChanges();
        transaction.Commit();
    }
    catch
    {
        transaction.Rollback();
    }
}

Конкретно в этом случае транзакция нафиг не нужна. SaveChanges создаст транзакцию автоматически.

Я про то что транзакции должны быть как можно короче.
Если ты хочешь все запараллелить чтобы ускориться, то обломись

А вот и не обломись:
public async Task<List<Blog>> GetBlogsAsync()
{
    using (var context = new BloggingContext())
    using (var context2 = new BloggingContext())
    {
         // parallel queries
    }
}
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации