#c# #task
Вопрос:
Почему код работает, даже если я не использую Wait()
метод для второй задачи, возвращаемой методом ContinueWith
?
Если я не использую Wait()
метод для первой задачи, то ничего не сработает, но не для второй.
using System;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
void MyMethod1()
{
Console.WriteLine(1);
}
void MyMethod2(Task MyTask)
{
Console.WriteLine(2);
}
Task MyTask1 = new Task(MyMethod1);
MyTask1.Start();
MyTask1.Wait();
Task MyTask2 = MyTask1.ContinueWith(MyMethod2); //Why it is work?
//MyTask2.Wait();
}
}
}
Комментарии:
1. Вы пробовали искать в Google
ContinueWith
? Первым хитом для меня стала документация, которая четко объясняет, что происходит. docs.microsoft.com/en-us/dotnet/api/…2. Вы вообще не должны использовать
.Wait()
. Итак: перестаньте это делать. Кроме того, настоятельно не рекомендуется использовать конструктор задач, который принимает делегат. В зависимости от того, что вы пытаетесь здесь сделать: вероятноTask.Run
, иawait
ваши друзья, возможно, с точкойstatic async Task Main()
входа. Вам тоже не должно быть нужно.ContinueWith
— простоawait
3. @Enigmativity, Если я задал вопрос, это означает, что я не понимаю, можете ли вы написать начало текста из статьи, которая все объясняет?
4. Дополнительная мысль: ничто в коде в вопросе не приносит отдаленной пользы от участия в задаче. Я собирался написать пример с использованием
await
, но : здесь действительно нечего ждать , если только вы намеренно не переключаете работу между потоками (например, чтобы разблокировать поток пользовательского интерфейса, который не применяется в случае консольного exe). Я не пытаюсь быть пренебрежительным здесь — совсем наоборот. Если вы можете пояснить, почему вы используете здесь задачи, возможно, будет более разумный и наглядный пример.5. Спасибо за ответы, но я еще не добрался до главы об асинхронном ожидании.
Ответ №1:
Это происходит потому, что консольное приложение завершает работу до того, как выходные данные будут записаны в консоль фоновыми задачами.
Если вы добавите Console.ReadLine()
в конец метода и попытаетесь запустить его без каких .Wait()
-либо изменений, вы увидите, что он выводится 1
и 2
, как и ожидалось.
Итак, почему же это происходит?
Ну, это потому, что с первой записью на консоль связаны накладные расходы — накладные расходы, которые не возникают при последующих записях.
Поэтому, когда у вас нет Wait()
первого Console.WriteLine()
, вы не успели закончить до выхода программы.
Однако, когда вы добавляете Wait()
к первой задаче, первая Console.WriteLine()
(очевидно) завершена, а вторая Console.WriteLine()
не имеет накладных расходов, связанных с первой, и у нее есть время закончить до выхода из программы.
(Это условие гонки, и фактические результаты могут отличаться в разных пробегах и в разных средах.)
Мы можем выполнить некоторые элементарные тайминги, чтобы показать разницу во времени, затраченном между первым вызовом Console.WriteLine()
и вторым вызовом. (Примечание: Обычно я использую скамейку.Нетто для таймингов, но это не очень хорошо сработало бы в данном конкретном случае.)
static void Main()
{
Stopwatch sw = Stopwatch.StartNew();
Console.WriteLine("1");
sw.Stop();
Console.WriteLine("First Console.WriteLine() took " sw.Elapsed);
sw.Restart();
Console.WriteLine("2");
sw.Stop();
Console.WriteLine("Second Console.WriteLine() took " sw.Elapsed);
}
Для сборки релиза на моем ПК это выводит:
1
First Console.WriteLine() took 00:00:00.0060951
2
Second Console.WriteLine() took 00:00:00.0000624
Как вы можете видеть, второй вызов почти в 100 раз быстрее, чем первый. Если программа завершится через несколько 0.0060951
секунд, вывод не будет виден.
Но если a Console.WriteLine()
ранее был выполнен, программа должна будет выйти менее чем 0.0000624
за секунды, чтобы вывод не появился (приблизительно!).
Вы также можете продемонстрировать это, добавив a Console.WriteLine()
в начало программы, чтобы последующие вызовы избегали накладных расходов при первом вызове:
Console.WriteLine("Priming");
void MyMethod1()
{
Console.WriteLine(1);
}
void MyMethod2(Task MyTask)
{
Console.WriteLine(2);
}
Task MyTask1 = new Task(MyMethod1);
MyTask1.Start();
Task MyTask2 = MyTask1.ContinueWith(MyMethod2); //Why it is work?
Если вы это сделаете, вывод будет 1
и 2
— даже без вызова MyTask1.Wait();
.
Опять же, обратите внимание, что это условие гонки, и фактические результаты могут отличаться. Он полагается на вызовы для WriteLine()
завершения до фактического завершения программы.
Ваш вывод из этого должен заключаться в том, что многопоточность сложна, и вы должны делать это правильно, иначе будут происходить странные вещи, подобные этому!
Комментарии:
1. Это стало немного понятнее для меня, но остались проблемы с точки зрения ресурсов процессора. Это мои догадки, но, возможно, вы сможете уточнить. Вариант 1. Если мы создадим одну задачу и запланируем ее с помощью метода «Пуск» и ничего больше — в консольном приложении, основной метод завершит работу до запуска нашей задачи. Потому что задача должна выполняться в новом потоке, а не в вызывающем потоке, и выделение ресурсов для нового потока занимает больше времени, чем требуется для завершения основного метода в вызывающем потоке. Правильно ли я понимаю?
2. Вариант 2. Мы используем вариант 1, но вызываем метод Wait из нашей задачи, который заставляет вызывающий поток ждать, пока наша задача не завершится в новом потоке. Здесь я уверен, что я прав. Вариант 3. Мы используем вариант 1, но сначала запускаем консоль. WriteLine («Грунтовка»), как вы написали. Тогда задача завершится в том же потоке, что и работа основного метода, то есть вызывающего, потому что для него уже выделены ресурсы? Это вопрос, правильно ли я понимаю?
3. Вариант 4. Мы используем вариант 2, из задачи мы называем MyTask1. Продолжайте с (MyMethod2), и мы также видим, что все работает даже без MyTask1. ContinueWith (MyMethod2).Wait(), также в отдельном потоке, в котором работала задача MyTask1, а не вызывающий поток метода main только потому, что ресурсы уже выделены для работы MyTask1. Правильно ли я понимаю? Бывают ли такие вещи?
4. Другими словами, чтобы гарантировать выполнение задачи, возвращающей ContinueWith (), нужно ли использовать метод ожидания?
5. @NikVladi Это правильно.