Лучший способ вызывать функции асинхронно - PullRequest
0 голосов
/ 04 апреля 2020

Я пытаюсь получить строку для асинхронной установки трех текстов меток. Сначала я попытался с этим

private async void button1_Click(object sender, EventArgs e)
{
    label1.Text = await SetTextbox(3);
    label2.Text = await SetTextbox(2);
    label3.Text = await SetTextbox(1);
}

private async Task<string> SetTextbox(int delaySeconds)
{
    Thread.Sleep(delaySeconds * 1000);
    return "Done.";
}

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

Сейчас я заставляю его работать с помощью Task.Run:

private async void button1_Click(object sender, EventArgs e)
{
     var proc1 = Task.Run(async () => { label1.Text = await SetTextbox(3); });
     var proc2 = Task.Run(async () => { label2.Text = await SetTextbox(2); });
     var proc3 = Task.Run(async () => { label3.Text = await SetTextbox(1); });

     await Task.WhenAll(proc1, proc2, proc3);
}

, но что-то подсказывает мне, что это неправильный способ достичь этого. Можете ли вы сказать мне, как лучше всего это сделать, или это хорошее решение?

Ответы [ 4 ]

2 голосов
/ 04 апреля 2020

Метод SetTextbox не является корректным асинхронным методом. Ожидаемое поведение асинхронного метода - немедленно вернуть Task. Блокировка вызывающей стороны с помощью Thread.Sleep нарушает это ожидание.

Теперь, если нам поступит плохо ведущий себя асинхронный метод, Task.Run - наш друг. Важно: единственное, что должно быть заключено в Task.Run, - это асинхронный метод блокировки. Любой связанный с пользовательским интерфейсом код должен оставаться снаружи:

private async void Button1_Click(object sender, EventArgs e)
{
     var task1 = Task.Run(async () => await BadBlockingMethodAsync(1));
     var task2 = Task.Run(async () => await BadBlockingMethodAsync(2));
     var task3 = Task.Run(async () => await BadBlockingMethodAsync(3));

     await Task.WhenAll(task1, task2, task3);

     label1.Text = await task1;
     label2.Text = await task2;
     label3.Text = await task3;
}

В качестве примечания: SetTextbox - неправильное имя для асинхронного метода. Согласно рекомендациям метод должен иметь суффикс Async (SetTextboxAsync).

1 голос
/ 05 апреля 2020

Чтобы превратить след комментария в ответ:

Метод

private async Task<string> SetTextbox(int delaySeconds)
{
    Thread.Sleep(delaySeconds * 1000);
    return "Done.";
}

совсем не асин c. Он никогда не выдаст поток (не ожидает), и вы должны увидеть предупреждение компилятора, которое пытается сообщить вам, что:

CS1998 Этот асин c метод не имеет операторов "ожидание" и будет работать синхронно. ...

И я предполагаю, что Thread.Sleep () специально предназначен для имитации синхронной (интенсивной загрузки ЦП) работы.

Немного лучше будет вариант:

private Task<string> SetTextbox(int delaySeconds)
{
    Thread.Sleep(delaySeconds * 1000);
    return Task.FromResult( "Done.");
}

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

Вариант, который лучше всего подходит к ситуации, просто

private string SetTextbox(int delaySeconds)
{
    Thread.Sleep(delaySeconds * 1000);
    return "Done.";
}

выполнение этого асинхронного c - это вопрос формы, а не сам метод. Это не асин c, у него нет ожидаемых частей.

Вы называете это так, когда хотите, чтобы они запускались последовательно:

label1.Text = await Task.Run(() => SetTextbox(1));
label2.Text = await Task.Run(() => SetTextbox(2));
label3.Text = await Task.Run(() => SetTextbox(3));

Это занимает 1 + 2 + 3 секунды, но ваша форма остается интерактивной, и вы видите, что «Готово» появляется через определенные промежутки времени. .

Чтобы выполнить их параллельно, на это уже был дан ответ, но вы можете устранить один слой async / await:

var task1 = Task.Run(() => SetTextbox(1));
var task2 = Task.Run(() => SetTextbox(2));
var task3 = Task.Run(() => SetTextbox(3));

await Task.WhenAll(task1, task2, task3);

label1.Text = await task1;
label2.Text = await task2;
label3.Text = await task3; 

, это, конечно, займет всего 3 секунды.

1 голос
/ 04 апреля 2020

Установка этих 3 меток не является зависимой операцией. В этом случае вы можете запустить все методы параллельно и получить их результат, как только все будет сделано, как

private async Task button1_Click(object sender, EventArgs e)
{
     var t1 = SetTextbox(3);
     var t2 = SetTextbox(2);
     var t3 = SetTextbox(1);

     string[] data = await Task.WhenAll(new[] { t1, t2, t3 });

    label1.Text = data[0];
    label2.Text = data[1];
    label3.Text = data[2];

} 
0 голосов
/ 04 апреля 2020

Асинхронный не означает, что он будет выполняться параллельно - это то, что вам нужно, если вы хотите, чтобы пользовательский интерфейс прекратил зависание. И именно поэтому Task.Run работает. Посмотрите на этот вопрос
Ваше решение Task.Run должно быть в порядке - это один из C# предполагаемых способов запуска чего-либо параллельно

...