Хто-небудь може пояснити, що await
робить функція?
Хто-небудь може пояснити, що await
робить функція?
Відповіді:
Вони щойно говорили про це в PDC вчора!
Await використовується разом із Завданнями (паралельне програмування) у .NET. Це ключове слово вводиться в наступній версії .NET. Це більш-менш дозволяє "призупинити" виконання методу, щоб дочекатися завершення Завдання. Ось короткий приклад:
//create and run a new task
Task<DataTable> dataTask = new Task<DataTable>(SomeCrazyDatabaseOperation);
//run some other code immediately after this task is started and running
ShowLoaderControl();
StartStoryboard();
//this will actually "pause" the code execution until the task completes. It doesn't lock the thread, but rather waits for the result, similar to an async callback
// please so also note, that the task needs to be started before it can be awaited. Otherwise it will never return
dataTask.Start();
DataTable table = await dataTask;
//Now we can perform operations on the Task result, as if we're executing code after the async operation completed
listBoxControl.DataContext = table;
StopStoryboard();
HideLoaderControl();
В основному, ключові слова async
та await
дозволяють вказати, що виконання методу повинно зупинятися при будь-якому використанні await
, яке позначає виклики асинхронних методів, а потім відновлюватися після завершення асинхронної операції. Це дозволяє вам викликати метод у головному потоці програми та обробляти складну роботу асинхронно, без необхідності чітко визначати потоки та об'єднання або блокувати основний потік програми.
Подумайте про це як про щось подібне до yield return
твердження у методі, що створює IEnumerable. Коли час виконання потрапляє в yield
, це в основному зберігає поточний стан методу і повертає отримане значення або посилання. Наступного разу, коли IEnumerator.MoveNext () буде викликаний для об'єкта return (який генерується внутрішньо середовищем виконання), старий стан методу відновлюється в стеку, і виконання продовжується з наступним рядком після, yield return
як ніби ми ніколи не залишали метод. Без цього ключового слова тип IEnumerator повинен бути визначений на замовлення для зберігання стану та обробки запитів на ітерацію з методами, які дійсно можуть стати ДУЖЕ складними.
Аналогічно, метод, позначений як, async
повинен мати принаймні один await
. У цей await
час середовище виконання збереже стан поточного потоку та стек викликів, зробить асинхронний виклик та повернеться назад до циклу повідомлень середовища виконання, щоб обробити наступне повідомлення та забезпечити відповідь програми. Коли асинхронна операція завершена, при наступній можливості планування стек викликів для збільшення асинхронної операції повертається назад і продовжується так, ніби виклик був синхронним.
Отже, ці два нові ключові слова в основному спрощують кодування асинхронних процесів, подібно до yield return
спрощеного генерування користувацьких перерахувань. Маючи пару ключових слів та трохи попередніх знань, ви можете пропустити всі заплутані та часто схильні до помилок деталі традиційного асинхронного шаблону. Це буде НЕОЦІНОВНО майже в будь-якому графічному інтерфейсі, керованому подіями, як Winforms, WPF Silverlight.
Наразі прийнята відповідь вводить в оману.
await
нічого не робить на паузі. Перш за все, його можна використовувати лише в методах або лямбдах, позначених як async
і повертаються, Task
або void
якщо вам все одно, що Task
екземпляр працює в цьому методі.
Ось ілюстрація:
internal class Program
{
private static void Main(string[] args)
{
var task = DoWork();
Console.WriteLine("Task status: " + task.Status);
Console.WriteLine("Waiting for ENTER");
Console.ReadLine();
}
private static async Task DoWork()
{
Console.WriteLine("Entered DoWork(). Sleeping 3");
// imitating time consuming code
// in a real-world app this should be inside task,
// so method returns fast
Thread.Sleep(3000);
await Task.Run(() =>
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine("async task iteration " + i);
// imitating time consuming code
Thread.Sleep(1000);
}
});
Console.WriteLine("Exiting DoWork()");
}
}
Вихід:
Увійшов у DoWork (). Сплячий 3
асинхронної завдання ітерація 0
Статус завдання: WaitingForActivation
Очікування ENTER
асинхронної завдання ітерація 1
асинхронної завдання ітерація 2
асинхронної завдання ітерація 3
асинхронної завдання ітерація 4
асинхронної завдання ітерація 5
асинхронної завдання ітерація 6
асинхронної завдання ітерація 7
асинхронної завдання ітерація 8
асинхронної завдання ітерація 9
Виходу Робити роботу()
await
включити? Значить, якщо це викликається з потоку інтерфейсу користувача, це блокуватиме інтерфейс інтерфейсу на 3 секунди? Ідея цієї моделі полягає в тому, щоб уникати подібних дій.
Для тих, хто новачок в асинхронному програмуванні в .NET, ось (абсолютно фальшива) аналогія у сценарії, який вам може бути більш знайомий - виклики AJAX за допомогою JavaScript / jQuery. Простий допис jQuery AJAX виглядає так:
$.post(url, values, function(data) {
// AJAX call completed, do something with returned data here
});
Причиною того, що ми обробляємо результати у функції зворотного виклику, є те, що ми не блокуємо поточний потік, очікуючи повернення виклику AJAX. Тільки тоді, коли відповідь буде готова, зворотний виклик спрацює, звільняючи поточний потік робити інші речі за цей час.
Тепер, якщо JavaScript підтримував await
ключове слово (що, звичайно, ще ( поки що! )), Ви могли б досягти того ж за допомогою цього:
var data = await $.post(url, values);
// AJAX call completed, do something with returned data here
Це набагато чистіше, але, схоже, ми ввели синхронний блокуючий код. Але (підроблений) компілятор JavaScript взяв би все після await
і підключив це до зворотного виклику, тому під час виконання другий приклад поводився б так само, як і перший.
Можливо, це не здається, що це економить вам багато роботи, але коли справа доходить до таких речей, як обробка винятків та контексти синхронізації, компілятор насправді робить для вас дуже важку роботу. Щоб отримати більше, я б порекомендував поширені запитання, а потім серію блогів Стівена Клірі .
Якби мені довелося реалізувати це на Java, це виглядало б приблизно так:
/**
* @author Ilya Gazman
*/
public abstract class SynchronizedTask{
private ArrayList<Runnable> listeners = new ArrayList<Runnable>();
private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(6, 6, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(1000));
public final void await(Runnable listener){
synchronized (this) {
listeners.add(listener);
}
}
public void excecute(){
onExcecute();
for (int i = listeners.size() - 1; i >= 0; i--) {
Runnable runnable;
synchronized (this) {
runnable = listeners.remove(i);
}
threadPoolExecutor.execute(runnable);
}
}
protected abstract void onExcecute();
}
Ваша програма використовувала б його так:
public class Test{
private Job job = new Job();
public Test() {
craeteSomeJobToRunInBackground();
methode1();
methode2();
}
private void methode1(){
System.out.println("Running methode 1");
job.await(new Runnable() {
@Override
public void run() {
System.out.println("Continue to running methode 1");
}
});
}
private void methode2(){
System.out.println("Running methode 2");
}
private void craeteSomeJobToRunInBackground() {
new Thread(new Runnable() {
@Override
public void run() {
job.excecute();
}
}).start();
}
private class Job extends SynchronizedTask{
@Override
protected void onExcecute() {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Job is done");
}
}
}