Що робить нова функція очікування C #? [зачинено]


83

Хто-небудь може пояснити, що awaitробить функція?




Хороші приклади також на dotnetperls.com/async .
Miljen Mikic

Я не вважаю, що це питання занадто широке або повинно бути закритим. Він запитує, що означає одне ключове слово. (Чи була якась інша версія якось іншою?)
Panzercrisis

Відповіді:


62

Вони щойно говорили про це в 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();

2
Коли це форма обіцянок C #: en.wikipedia.org/wiki/Futures_and_promises

12
Звучить дуже схоже на Thread.Join ().
Steve Guidi

10
Нагадує мені COMEFROM
Джоель

20
Для повноти додамо, що вищезазначений фрагмент коду повинен бути обернутий у метод, який прикрашений ключовим словом async. Цей метод негайно повертається, як тільки в ньому зустрічається перше ключове слово await.
Przemek

14
Вашими словами: це дозволяє "призупинити" метод, але слід зазначити, що він не призупиняє і не блокує потік.
Matt Crinklaw-Vogt

47

В основному, ключові слова asyncта awaitдозволяють вказати, що виконання методу повинно зупинятися при будь-якому використанні await, яке позначає виклики асинхронних методів, а потім відновлюватися після завершення асинхронної операції. Це дозволяє вам викликати метод у головному потоці програми та обробляти складну роботу асинхронно, без необхідності чітко визначати потоки та об'єднання або блокувати основний потік програми.

Подумайте про це як про щось подібне до yield returnтвердження у методі, що створює IEnumerable. Коли час виконання потрапляє в yield, це в основному зберігає поточний стан методу і повертає отримане значення або посилання. Наступного разу, коли IEnumerator.MoveNext () буде викликаний для об'єкта return (який генерується внутрішньо середовищем виконання), старий стан методу відновлюється в стеку, і виконання продовжується з наступним рядком після, yield returnяк ніби ми ніколи не залишали метод. Без цього ключового слова тип IEnumerator повинен бути визначений на замовлення для зберігання стану та обробки запитів на ітерацію з методами, які дійсно можуть стати ДУЖЕ складними.

Аналогічно, метод, позначений як, asyncповинен мати принаймні один await. У цей awaitчас середовище виконання збереже стан поточного потоку та стек викликів, зробить асинхронний виклик та повернеться назад до циклу повідомлень середовища виконання, щоб обробити наступне повідомлення та забезпечити відповідь програми. Коли асинхронна операція завершена, при наступній можливості планування стек викликів для збільшення асинхронної операції повертається назад і продовжується так, ніби виклик був синхронним.

Отже, ці два нові ключові слова в основному спрощують кодування асинхронних процесів, подібно до yield returnспрощеного генерування користувацьких перерахувань. Маючи пару ключових слів та трохи попередніх знань, ви можете пропустити всі заплутані та часто схильні до помилок деталі традиційного асинхронного шаблону. Це буде НЕОЦІНОВНО майже в будь-якому графічному інтерфейсі, керованому подіями, як Winforms, WPF Silverlight.


31

Наразі прийнята відповідь вводить в оману. 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
Виходу Робити роботу()


1
Ви також знаєте, що він заблокує абонента на 3 секунди, перш ніж він навіть дасть їм завдання, яке вони можуть awaitвключити? Значить, якщо це викликається з потоку інтерфейсу користувача, це блокуватиме інтерфейс інтерфейсу на 3 секунди? Ідея цієї моделі полягає в тому, щоб уникати подібних дій.
Серві

1
@Servy так, в цьому була суть. Показати всі етапи виконання. Це не реальний приклад.
Анрі

7
@ Серві, ти мене тролюєш?
Анрі

2
Ні. Я намагаюся допомогти вам покращити свою відповідь.
Серві

2
@ Анрі ... Я дуже ціную ваші зусилля тут. Дуже дякую!!
Praveen Prajapati

11

Для тих, хто новачок в асинхронному програмуванні в .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і підключив це до зворотного виклику, тому під час виконання другий приклад поводився б так само, як і перший.

Можливо, це не здається, що це економить вам багато роботи, але коли справа доходить до таких речей, як обробка винятків та контексти синхронізації, компілятор насправді робить для вас дуже важку роботу. Щоб отримати більше, я б порекомендував поширені запитання, а потім серію блогів Стівена Клірі .


Дотримуючись цієї фальшивої аналогії (яка мені дуже допомогла, до того ж, дякую!), Що ви маєте на увазі "все після"? Все, що знаходиться в межах однієї функції (методу)? Або все після нього, як і будь-що взагалі, що могло бути додано до стека викликів?

2
"Все після" = решта методу. Компілятор ефективно перезаписує решту методу як зворотний виклик, а керування негайно повертається до абонента поточного методу.
Тодд Меньє,

1
Блискучий Тодд, ще раз дякую за пояснення. Впевнений, корисний і іншим.

-2

Якби мені довелося реалізувати це на 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");
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.