А вы мне предлагаете как ни в чём ни бывало игнорировать зависший или по ошибке медленно работающий таск, вместо того, чтобы явно обнаружить проблему при тестировании программы.
Вы делайте, как хотите, а я лучше сразу обнаружу проблему и исправлю.
Это не смешная шутка, даже я со своим далеко не самым глубоким знанием тасков прекрасно понимаю разницу. Если метод асинхронный, то подобные блокирующие вызовы в нём нужно оборачивать в таски, иначе толку от асинхронности метода никакой.
Можете сами перепроверить, если мои результаты не внушают доверия. Для этого скомпилируйте и запустите приложение, создайте пять-десять документов, закройте через команду «Выход», а затем переоткройте и понаблюдайте за прогресс-барами, которые отображают процесс загрузки на вкладке каждого документа.
Понял вас. На самом деле, идея расширения более простая. Это, во-первых, замена обычно цикла foreach для коллекций на метод, как у List'а, а, во-вторых, возможность вернуть коллекцию дальше в цепочку вызовов.
Конкретно таски она затрагивает лишь косвенно. С таким же успехом я мог бы использовать стандартный метод класса List и писать в нём async...await.
Я учёл ваши замечания насчёт Load и переписал через WhenAll. Теперь всё работает параллельно и асинхронно (при добавлении рандомных задержек в метод, прогресс-бары скрываются в разные моменты времени для каждого документа). Одобряете такой код?
По-видимому, вы оказались правы в том, что await ничего не запускает, даже холодный таск. Такой код у меня вывел только Start. Хотя, может, я что-то и упустил. )
static async Task LoadAsync() => await new Task(() => System.Console.WriteLine("Load Async"));
static Task Load() => new Task(() => System.Console.WriteLine("Load"));
static async void Test()
{
System.Console.WriteLine("Start");
await LoadAsync();
await Load();
System.Console.WriteLine("Finish");
}
public static void Main()
{
Test();
var i = 0;
while (true)
{
i++;
}
}
По умолчанию принято считать что любой таск когда-нибудь выполнится если только обратное не написано в документации.
то для меня вдвойне очевидно, что загрузка выполнится асинхронно в случае работы с тасками, потому что никаких WaitAll я не делаю. А поскольку и в других местах используются подобные конструкции, например, с Close (где её убрать нельзя), то для общности мне хочется оставить её и с Load, путь даже это чуть менее оптимально с точки зрения компиляции.
Конечно, если вы видите более серьёзные потенциальные проблемы в виде блокировок, например, то поделитесь…
Знаете, почему вообще появились многие идеи из текущей статьи? Меня просто обескураживают некоторые детали «обычного паттерн-матчинга», а с помощью кастомных расширений я могу избежать их применения в своём коде, пусть даже ценой небольшого падения производительности (во многих случаях это для меня не критично).
Если бы мне в нём всё сразу понравилось, то никаких альтернатив точно бы не изобретал.
Последнее уточнение, для случая ...ForEach(async d => await d.Load())
компилятор сгененирует ощутимо менее оптимальный код, чем для ...ForEach(d => d.Load()), поэтому второй вариант предпочтительнее? Или дело в другом?
И для меня, в первую очередь, важна предсказуемость встроенных языковых средств, чтобы интуитивно заменив IModel на var где это, в привычном понимании, равноценная замена, не получить другое поведение программы.
Никак вам не различить эти ситуации. То, когда начнется выполнение, зависит от того, что внутри AnyTask, а не снаружи.
Если правильно понимаю вас, то при наличии интерфейса, как у меня, и абстрагируясь от конкретной имплементации документа, мне нужно явно указывать await у Load, чтобы гарантированно выполнить таск, верно? Или чего-то ещё не понимаю?
Вы делайте, как хотите, а я лучше сразу обнаружу проблему и исправлю.
Documents.ForEach1(d => d.Expose())
.ToList().ForEach(async d => await d.Load());
Documents.ForEach1(d => d.Expose())
.ToList().ForEach(d => d.Load());
Асинхронность отслеживаю визуально по состоянию прогресс-баров. Задержки добавляю случайно в классе PlainTextDocument
Можете сами перепроверить, если мои результаты не внушают доверия. Для этого скомпилируйте и запустите приложение, создайте пять-десять документов, закройте через команду «Выход», а затем переоткройте и понаблюдайте за прогресс-барами, которые отображают процесс загрузки на вкладке каждого документа.
Кстати, на практике асинхронно и параллельно у меня работают и все вариации с ForEach (c async и без, у List и как своё расширение).
foreachдля коллекций на метод, как уList'а, а, во-вторых, возможность вернуть коллекцию дальше в цепочку вызовов.Конкретно таски она затрагивает лишь косвенно. С таким же успехом я мог бы использовать стандартный метод класса
Listи писать в нёмasync...await.Я учёл ваши замечания насчёт
Loadи переписал черезWhenAll. Теперь всё работает параллельно и асинхронно (при добавлении рандомных задержек в метод, прогресс-бары скрываются в разные моменты времени для каждого документа). Одобряете такой код?то для меня вдвойне очевидно, что загрузка выполнится асинхронно в случае работы с тасками, потому что никаких WaitAll я не делаю. А поскольку и в других местах используются подобные конструкции, например, с Close (где её убрать нельзя), то для общности мне хочется оставить её и с Load, путь даже это чуть менее оптимально с точки зрения компиляции.
Конечно, если вы видите более серьёзные потенциальные проблемы в виде блокировок, например, то поделитесь…
async...awaitчеловек понимает, что с тасками идёт работа.В случае же
LoadAsync, можно подумать, что я вообще по старинке вручную создаю поток без всяких тасков.Всего лишь делюсь результатами.
Если бы мне в нём всё сразу понравилось, то никаких альтернатив точно бы не изобретал.
...ForEach(async d => await d.Load())мне срузу становится ясно, что загрузка идёт асинхронная, а вот
...ForEach(d => d.Load())ни о чём не говорит, нужно лезть в имплементацию или заранее именовать методы, как LoadAsync (при условии, что это мой код, а не чужой).
Из этих соображений читаемости для меня сейчас всё равно предпочтительным остаётся первый вариант…
Последнее уточнение, для случая
...ForEach(async d => await d.Load())компилятор сгененирует ощутимо менее оптимальный код, чем для
...ForEach(d => d.Load()), поэтому второй вариант предпочтительнее? Или дело в другом?И для меня, в первую очередь, важна предсказуемость встроенных языковых средств, чтобы интуитивно заменив IModel на var где это, в привычном понимании, равноценная замена, не получить другое поведение программы.