Комментарии 19
Сделать вызов можно просто не написав ключевое слово await
. Без await
не нужен async
.
В чём суть\смысл?
ПС: у вас есть Wait
в коде и есть ContinueWith
без ожидания. Вы бросаете во все стороны задачи и оно как-то работает. Выглядит как Fire-and-forget, почти никогда в приложении не надо так делать.
Вот написал асинхронный UI, но форма блочится. Что не так?
код
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private Task asyncTask1()
{
int n1 = 0;
while (n1 < 10000000)
{
n1++;
textBox1.Text = n1.ToString();
textBox1.Update();
}
return Task.CompletedTask;
}
private async void button1_Click(object sender, EventArgs e)
{
await asyncTask1();
}
}
перегрузка очереди сообщений единственного UI-ного треда.
Вы ему напихали сообщений апдейтов ТекстБокса по самое не могу, и он ими и подавился, образно выражаясь.
это не для async/await компиляция, тут надо понимать во что вот это
textBox1.Text = n1.ToString();
textBox1.Update();
компилируется.
Нет у вас асинхронности. Просто использование async-await не делает никакой магии.
Вы синхронно крутите while, вот форма и висит.
точно!
Из button1_Click
мы вышли для выполнения asyncTask1()
(даже если), но сразу зашли в asyncTask1()
и в ней повисли
Как крутить while отдельно от основной задачи? While тут для имитации действий, где нельзя срезать углы, т.е. все операции и вычисления должны быть произведены полностью и без блокировки формы.
Не обязательно на отдельном вычислительном ядре, ну хоть в отдельном таймслайсе.
Запустите отдельную таску (через Task.Run например).
Чтобы из неё обновлять UI - захватите Dispatcher.CurrentDispatcher
из формы в Task-у и передавайте туда изменения. Что-то типа такого должно работать - https://stackoverflow.com/a/16007664
Спасибо за подсказку, вот рабочий вариант.
Hidden text
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private Task asyncTask1()
{
int n1 = 0;
while (n1 < 1000000000)
{
n1++;
Invoke(new Action(() => { textBox1.Text = n1.ToString(); }));
Thread.Sleep(1000); // чтобы не спамил в UI
}
return Task.CompletedTask;
}
private async void button1_Click(object sender, EventArgs e)
{
await Task.Run(() => asyncTask1());
}
}
textBox1.Update() не нужен, UI-поток сам обновляет контролы.
Но было такое ощущение, что async/await уже научили задействовать таски под капотом...
Но было такое ощущение, что async/await уже научили задействовать таски под капотом...
Они немного про разное.
Таска - это отдельный объем работы, обычно выполняемый на тред-пуле (т.е. выполняемый параллельно некоему "основному" треду, в котором создается таска).
async-await - это возможность переиспользовать тред, пока некий запущенный "объем работы" ждёт ответа от внешних источников (сеть, диск). Если сети или диска нет, то async-await в целом не нужен.
И в примерах тоже ждут ответа от внешнего ресурса. Но в чем принципиальное отличие между ожиданием ответа сервера и выполнением вычислений? В обоих случаях крутится некоторый объем кода.
А это в целом тема любой статьи по async-await.
Как работало раньше - был вызов сети или диска, который блокировал поток. При этом в реальности операционная система не делала ничего на ЦПУ (в приложении) и поток с точки зрения приложения был занят, но не использовался.
Как оптимизировали сценарий с async-await. Когда вызов доходит до операционной системы, поток c# приложения возвращается в тред пул (если он из тред пула) и любой другой выполняемый Task может на свободном потоке поработать. Когда ОС сигналит что данные получены (с диска, из сети) - с тред пула берётся просто свободный поток и в нём продолжается выполнение.
Есть особенности, когда что-то идёт по другому, но поэтому я и выше указал, что это тема целых статей. Много мелких особенностей.
Ну тогда я еще спрошу.
await Task.Run(() => asyncTask1());
await Task.Run(new Action(() => asyncTask1()));
Оба варианта вызова дают один и тот же результат, а принципиальная разница в вызове есть? В этих двух случаях анонимная функция без параметров () =>{} принимает тип Func<Task> и Action. А без лямбды можно запустить таску с функцией asyncTask1()? Или без оборачивания в лямбду не обойтись?
насколько я понимаю это одно и тоже, но можно еще проще написать:
await Task.Run(asyncTask1);
и этот вариант может компилироваться в более эффективный код, но в любом случае в вариант не хуже по эффективности.
Лямбды нужны там где нет делегатов.
Если метод не имеет параметров, то можно просто обернуть его в Task.Run:
Task.Run(Console.WriteLine);
даже если вы честно создадите отдельный поток для вашей функции с while-ом, все равно ничего хорошего не получится, как раз из-за того что я сначала написал:
перегрузка очереди сообщений единственного UI-ного треда.
нельзя без ограничений (непрерывно) генерировать Апдейты на UI, то есть написать-то можно, только ничего хорошего из этого не получится, вы ответьте себе на вопрос с какой максимальной частотой у вас должна обновляться картинка на экране, то есть сколько раз в секунду. У вас тут нет ограничения, поэтому вы упираетесь в физические пределы параметров работы оборудования (видеокарты- монитора).
нашел время внимательно прочитать то что вы написали,
насколько я понимаю в этом вашем примере инкремент:
n1++;
симулирует некоторую, достаточно легковесную вычислительную работу, которую вы хотели бы выполнять непосредственно в UI потоке (при условии что она легковесная это можно делать).
Если это так то скорее всего вам надо бы объявить и вашу функцию:
private Task asyncTask1()
аинхронной, сделать await этой вашей операции с работой, но чтобы await работал - обеспечивал выход из функции (из скомпилированной стайт-машины) надо добавить например
Thread.Sleep(0); в конец этой вашей работы, как-то так:
private async Task asyncTask1()
{
int n1 = 0;
while (n1 < 10000000)
{
n1++;
textBox1.Text = n1.ToString();
textBox1.Update();
await Task.Delay(1);
}
return Task.CompletedTask;
}
я не уверен что все до конца корректно написал, нет возможности скомпилировать - проверить, но надеюсь идея понятна:
идея в том чтобы заставить инкремент и изменение текстового поля выполняться по очереди внутри единственного потока.
Вроде как инкремент выполнится и пошлет изменение контролу,
await прервет итерацию стейт машины,
UI поток выполнит апдейт,
UI поток продолжит выполнение функции asyncTask1()
и так по кругу.
Вообще говоря
await Thread.Sleep(0);
можно воткнуть в любое место в цикле, мне кажется.
Async/Await в C#. Часть 5. Функция-перечисление и цикл через рекурсию, асинхронный вызов без Async/Await