Подтвердить что ты не робот

Какой хороший несетевой пример новой функции С# Async?

Microsoft только что анонсировала новую функцию С# Async. Каждый пример, который я видел до сих пор, касается асинхронной загрузки чего-либо из HTTP. Конечно, есть и другие важные асинхронные вещи?

Предположим, что я не пишу новый клиент RSS или приложение Twitter. Что интересно для С# Async для меня?

Изменить У меня был Ага! при просмотре сеанса PDC Андерса. Раньше я работал над программами, использующими потоки "наблюдателей". Эти потоки ждут, когда что-то случится, например, наблюдая за изменением файла. Они не выполняют работу, просто простаивают и уведомляют основной поток, когда что-то происходит. Эти потоки могут быть заменены кодом await/async в новой модели.

4b9b3361

Ответ 1

О, это звучит интересно. Я еще не играю с CTP, просто просматривая документ. После просмотра Андерса Хейлсберга об этом, я думаю, я могу видеть, как это может оказаться полезным.

Как я понимаю, async упрощает чтение и реализацию написания асинхронных вызовов. Точно так же писать итераторы проще сейчас (в отличие от написания функций вручную). Это важные блокирующие процессы, так как никакая полезная работа не может быть выполнена, пока она не будет разблокирована. Если вы загружаете файл, вы не можете сделать ничего полезного, пока не получите этот файл, чтобы поток стал пустым. Подумайте, как можно назвать функцию, которая, как вы знаете, блокирует неопределенную длину и возвращает некоторый результат, затем обрабатывает ее (например, сохраняет результаты в файле). Как бы вы это написали? Вот простой пример:

static object DoSomeBlockingOperation(object args)
{
    // block for 5 minutes
    Thread.Sleep(5 * 60 * 1000);

    return args;
}

static void ProcessTheResult(object result)
{
    Console.WriteLine(result);
}

static void CalculateAndProcess(object args)
{
    // let calculate! (synchronously)
    object result = DoSomeBlockingOperation(args);

    // let process!
    ProcessTheResult(result);
}

Хорошо, мы его реализовали. Но подождите, для расчета потребуется несколько минут. Что делать, если мы хотели иметь интерактивное приложение и делать другие вещи во время вычисления (например, для создания пользовательского интерфейса)? Это нехорошо, так как мы вызывали функцию синхронно, и нам нужно дождаться ее, чтобы закончить эффективное замораживание приложения, так как поток ожидает разблокировки.

Ответ. Вызовите функцию дорогой функции асинхронно. Таким образом, мы не обязаны ждать завершения операции блокировки. Но как мы это делаем? Мы бы вызывали функцию асинхронно и регистрировали функцию обратного вызова, которая вызывается при разблокировке, чтобы мы могли обработать результат.

static void CalculateAndProcessAsyncOld(object args)
{
    // obtain a delegate to call asynchronously
    Func<object, object> calculate = DoSomeBlockingOperation;

    // define the callback when the call completes so we can process afterwards
    AsyncCallback cb = ar =>
        {
            Func<object, object> calc = (Func<object, object>)ar.AsyncState;
            object result = calc.EndInvoke(ar);

            // let process!
            ProcessTheResult(result);
        };

    // let calculate! (asynchronously)
    calculate.BeginInvoke(args, cb, calculate);
}
  • Примечание. Конечно, мы могли бы запустить другой поток, чтобы это сделать, но это означало бы, что мы создаем поток, который просто сидит там, ожидая разблокировки, а затем сделайте полезную работу. Это будет пустой тратой.

Теперь вызов асинхронный, и нам не нужно беспокоиться о ожидании завершения и обработки вычисления, которое выполняется асинхронно. Он закончит, когда сможет. Альтернативно для вызова асинхронно вызывающего кода вы можете использовать задачу:

static void CalculateAndProcessAsyncTask(object args)
{
    // create a task
    Task<object> task = new Task<object>(DoSomeBlockingOperation, args);

    // define the callback when the call completes so we can process afterwards
    task.ContinueWith(t =>
        {
            // let process!
            ProcessTheResult(t.Result);
        });

    // let calculate! (asynchronously)
    task.Start();
}

Теперь мы называем нашу функцию асинхронно. Но что нужно, чтобы добиться этого? Прежде всего, нам нужно, чтобы делегат/задача, чтобы иметь возможность называть его асинхронно, нам нужна функция обратного вызова, чтобы иметь возможность обрабатывать результаты, а затем вызывать функцию. Мы перешли на вызов с двумя линиями, чтобы гораздо больше просто назвать что-то асинхронно. Мало того, логика в коде стала более сложной, чем это было или могло быть. Хотя использование задачи помогло упростить процесс, нам все равно нужно было сделать что-то, чтобы это произошло. Мы просто хотим запустить асинхронно, а затем обработать результат. Почему мы не можем это сделать? Ну, теперь мы можем:

// need to have an asynchronous version
static async Task<object> DoSomeBlockingOperationAsync(object args)
{
    //it is my understanding that async will take this method and convert it to a task automatically
    return DoSomeBlockingOperation(args);
}

static async void CalculateAndProcessAsyncNew(object args)
{
    // let calculate! (asynchronously)
    object result = await DoSomeBlockingOperationAsync(args);

    // let process!
    ProcessTheResult(result);
}

Теперь это был очень упрощенный пример с простыми операциями (вычисление, процесс). Представьте себе, что каждая операция не может быть удобно помещена в отдельную функцию, но вместо этого есть сотни строк кода. Это большая сложность, чтобы получить выгоду от асинхронного вызова.


Другим практическим примером, используемым в документе, является использование его в приложениях пользовательского интерфейса. Изменено, чтобы использовать приведенный выше пример:

private async void doCalculation_Click(object sender, RoutedEventArgs e) {
    doCalculation.IsEnabled = false;
    await DoSomeBlockingOperationAsync(GetArgs());
    doCalculation.IsEnabled = true;
}

Если вы сделали какое-либо программирование пользовательского интерфейса (будь то WinForms или WPF) и попытались вызвать дорогостоящую функцию в обработчике, вы поймете, что это удобно. Использование фона рабочего для этого было бы не так полезно, поскольку фоновый поток будет сидеть там, пока он не сможет работать.


Предположим, что у вас есть способ контролировать какое-то внешнее устройство, скажем, принтер. И вы хотели перезапустить устройство после сбоя. Естественно, потребуется некоторое время для запуска принтера и готовности к работе. Возможно, вам придется учитывать перезагрузку, не помогая и снова пытаться перезапустить. У вас нет выбора, кроме как ждать его. Нет, если вы сделали это асинхронно.

static async void RestartPrinter()
{
    Printer printer = GetPrinter();
    do
    {
        printer.Restart();

        printer = await printer.WaitUntilReadyAsync();

    } while (printer.HasFailed);
}

Представьте себе запись цикла без async.


Последний пример. Представьте себе, что вам нужно было выполнить несколько операций блокировки в функции и вы хотите вызвать асинхронно. Что бы вы предпочли?

static void DoOperationsAsyncOld()
{
    Task op1 = new Task(DoOperation1Async);
    op1.ContinueWith(t1 =>
    {
        Task op2 = new Task(DoOperation2Async);
        op2.ContinueWith(t2 =>
        {
            Task op3 = new Task(DoOperation3Async);
            op3.ContinueWith(t3 =>
            {
                DoQuickOperation();
            }
            op3.Start();
        }
        op2.Start();
    }
    op1.Start();
}

static async void DoOperationsAsyncNew()
{
    await DoOperation1Async();

    await DoOperation2Async();

    await DoOperation3Async();

    DoQuickOperation();
}

Прочитайте документ , на самом деле у него много практических примеров, например, написания параллельных задач и других.

Я не могу дождаться, когда начну играть с этим либо в CTP, либо когда .NET 5.0 окончательно сделает это.

Ответ 2

Основными сценариями являются любой сценарий, который предполагает высокую задержку. То есть, много времени между "запросить результат" и "получить результат". Сетевые запросы являются наиболее очевидным примером сценариев с высокой задержкой, а в целом - ввода-вывода, а затем длинными вычислениями, связанными с ЦП на другом ядре.

Однако есть потенциально другие сценарии, с которыми эта технология будет хорошо взаимодействовать. Например, рассмотрим сценарий логики игры FPS. Предположим, у вас есть обработчик события нажатия кнопки. Когда игрок нажимает кнопку, вы хотите играть сирену в течение двух секунд, чтобы предупредить врагов, а затем откройте дверь на десять секунд. Было бы неплохо сказать что-то вроде:

button.Disable();
await siren.Activate(); 
await Delay(2000);
await siren.Deactivate();
await door.Open();
await Delay(10000);
await door.Close();
await Delay(1000);
button.Enable();

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

Ответ 3

Сегодня я нашел еще один приятный случай использования: вы можете ждать взаимодействия с пользователем.

Например, если в одной форме есть кнопка, которая открывает другую форму:

Form toolWindow;
async void button_Click(object sender, EventArgs e) {
  if (toolWindow != null) {
     toolWindow.Focus();
  } else {
     toolWindow = new Form();
     toolWindow.Show();
     await toolWindow.OnClosed();
     toolWindow = null;
  }
}

Конечно, это на самом деле не проще, чем

toolWindow.Closed += delegate { toolWindow = null; }

Но я думаю, что это хорошо демонстрирует, что может сделать await. И как только код в обработчике событий нетривиален, await упрощает программирование. Подумайте о том, что пользователю нужно щелкнуть последовательность кнопок:

async void ButtonSeries()
{
  for (int i = 0; i < 10; i++) {
    Button b = new Button();
    b.Text = i.ToString();
    this.Controls.Add(b);
    await b.OnClick();
    this.Controls.Remove(b);
  }
}

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

Помните, что await можно использовать со всем, что будет завершено в какой-то момент в будущем. Здесь используется метод расширения Button.OnClick(), чтобы сделать работу выше:

public static AwaitableEvent OnClick(this Button button)
{
    return new AwaitableEvent(h => button.Click += h, h => button.Click -= h);
}
sealed class AwaitableEvent
{
    Action<EventHandler> register, deregister;
    public AwaitableEvent(Action<EventHandler> register, Action<EventHandler> deregister)
    {
        this.register = register;
        this.deregister = deregister;
    }
    public EventAwaiter GetAwaiter()
    {
        return new EventAwaiter(this);
    }
}
sealed class EventAwaiter
{
    AwaitableEvent e;
    public EventAwaiter(AwaitableEvent e) { this.e = e; }

    Action callback;

    public bool BeginAwait(Action callback)
    {
        this.callback = callback;
        e.register(Handler);
        return true;
    }
    public void Handler(object sender, EventArgs e)
    {
        callback();
    }
    public void EndAwait()
    {
        e.deregister(Handler);
    }
}

К сожалению, не представляется возможным добавить метод GetAwaiter() непосредственно к EventHandler (разрешая await button.Click;), потому что тогда метод не знал бы, как регистрировать/отменять регистрацию этого события. Это немного шаблона, но класс AwaitableEvent можно повторно использовать для всех событий (а не только для пользовательского интерфейса). И с незначительной модификацией и добавлением некоторых дженериков вы можете разрешить получение EventArgs:

MouseEventArgs e = await button.OnMouseDown();

Я мог видеть, что это полезно для некоторых более сложных жестов пользовательского интерфейса (drag'n'drop, жестов мыши,...) - хотя вам придется добавить поддержку для отмены текущего жеста.

Ответ 4

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

И это применимо ко всем многопоточным/параллельным проблемным областям (которые уже существуют).

Async и Await - это новый (более простой) способ структурирования всего параллельного кода, будь то связанный с CPU или привязанный ввод/вывод. Самое большое улучшение в областях, где до С# 5 вам приходилось использовать модель APM (IAsyncResult) или модель события (BackgroundWorker, WebClient). Я думаю, именно поэтому эти примеры ведут парад.

Ответ 5

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

 while true do
    sleep for 1 second
    display the new time on the clock

и await (или с асинхронным F #), чтобы асинхронно спать, вы можете записать этот код для запуска в потоке пользовательского интерфейса неблокирующим способом.

http://lorgonblog.wordpress.com/2010/03/27/f-async-on-the-client-side/

Ответ 6

Расширения async полезны в некоторых случаях, когда у вас асинхронная операция. Асинхронная операция имеет определенный старт и завершение. Когда асинхронные операции завершены, они могут иметь результат или ошибку. (Отмена рассматривается как особый вид ошибки).

Асинхронные операции полезны в трех ситуациях (в широком смысле):

  • Сохранение пользовательского интерфейса. Каждый раз, когда у вас есть длительная работа (независимо от того, связана ли CPU или привязана к вводу/выводу), сделайте ее асинхронной.
  • Масштабирование серверов. Использование асинхронных операций, разумно на стороне сервера, может помочь масштабируемым вашим секундам. например, асинхронные страницы ASP.NET могут использовать операции async. Тем не менее, это не всегда выигрыш; вам нужно сначала оценить узкие места вашей масштабируемости.
  • Предоставление чистого асинхронного API в библиотеке или общем коде. async отлично подходит для повторного использования.

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

Существует несколько типов concurrency, где async не лучший инструмент:

  • распараллеливания. Параллельный алгоритм может использовать многие ядра (процессоры, графические процессоры, компьютеры) для более быстрого решения проблемы.
  • Асинхронные события. Асинхронные события происходят постоянно, независимо от вашей программы. У них часто нет "завершения". Обычно ваша программа будет подписываться на асинхронный поток событий, получать некоторое количество обновлений и затем отказаться от подписки. Ваша программа может обрабатывать подписку и отменять подписку как "начало" и "завершение", но фактический поток событий никогда не останавливается.

Параллельные операции лучше всего выражаются с помощью PLINQ или Parallel, поскольку у них есть много встроенной поддержки для секционирования, ограниченного concurrency и т.д. Параллельная операция может быть легко завершена в ожидании, запустив ее из ThreadPool поток (Task.Factory.StartNew).

Асинхронные события плохо сопоставляются с асинхронными операциями. Одна из проблем заключается в том, что асинхронная операция имеет единственный результат в точке завершения. Асинхронные события могут иметь любое количество обновлений. Rx - это естественный язык для работы с асинхронными событиями.

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

Ответ 7

Вот, вероятно, хороший пример того, как не использовать новую функцию асинхронизации (которая не пишет новый клиент RSS или приложение Twitter), точки перегрузки среднего метода в вызове виртуального метода. Честно говоря, я не уверен, что есть какой-либо способ создать более чем одну точку перегрузки для каждого метода.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace AsyncText
{
    class Program
    {
        static void Main(string[] args)
        {
            Derived d = new Derived();

            TaskEx.Run(() => d.DoStuff()).Wait();

            System.Console.Read();
        }
        public class Base
        {
            protected string SomeData { get; set; }

            protected async Task DeferProcessing()
            {
                await TaskEx.Run(() => Thread.Sleep(1) );
                return;
            }
            public async virtual Task DoStuff() {
                Console.WriteLine("Begin Base");
                Console.WriteLine(SomeData);
                await DeferProcessing();
                Console.WriteLine("End Base");
                Console.WriteLine(SomeData);
            }
        }
        public class Derived : Base
        {
            public async override Task DoStuff()
            {
                Console.WriteLine("Begin Derived");
                SomeData = "Hello";
                var x = base.DoStuff();
                SomeData = "World";
                Console.WriteLine("Mid 1 Derived");
                await x;
                Console.WriteLine("EndDerived");
            }
        }
    }
}

Выход:

Начало производного

Начало базы

Hello

Среднее значение 1 Производится

Конечная база

Мир

EndDerived

С определенными иерархиями наследования (а именно с использованием шаблона команды) я нахожусь в желании иногда делать такие вещи.

Ответ 8

здесь статья о том, как использовать синтаксис "асинхронный" в несетевом сценарии, который включает в себя пользовательский интерфейс и несколько действий.