2010-10-28 10 views
20

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

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

Редактировать У меня был Aha! момент во время просмотра Anders' PDC session. Раньше я работал над программами, использующими потоки «наблюдателей». Эти потоки ждут, когда что-то случится, например, наблюдая за изменением файла. Они не выполняют работу, просто простаивают и уведомляют основной поток, когда что-то происходит. Эти потоки могут быть заменены на ожидание/асинхронный код в новой модели.

+0

Или ставить вопрос в более общем плане, когда асинхронно полезно? Почти любой общий пример асинхронного анализа должен иметь отношение к тому, что было показано на C# 5. – Larsenal

+0

Когда вы говорите «не-сетевые», вы действительно имеете в виду «не-I/O»? Потому что действительно все I/O могут блокироваться. – Gabe

+0

+1 Предположим, что я делясь потоками данных из сети, но через трехсторонний терминал, и я не знаю, что находится внутри терминала или как он получает данные из сети, я просто использую его dll и имею все данные в моей программе, появляющейся асинхронно как магия. Не нужно ничего знать о сети. Но мне нужно знать async/await –

ответ

25

О, это звучит интересно. Я еще не играю с CTP, просто просматривая документ. Увидев Anders Hejlsberg's talk, я думаю, что могу видеть, как это может оказаться полезным.

Как я понимаю, 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's calculate! (synchronously) 
    object result = DoSomeBlockingOperation(args); 

    // let's 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's process! 
      ProcessTheResult(result); 
     }; 

    // let's 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's process! 
      ProcessTheResult(t.Result); 
     }); 

    // let's 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's calculate! (asynchronously) 
    object result = await DoSomeBlockingOperationAsync(args); 

    // let's 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); 
} 

Представьте себе, что пишут без асинхронного цикла.


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

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(); 
} 

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

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

+0

Я не уверен, что синтаксис - это 100% звук с использованием async, но вы получаете эту идею. –

+0

В случае с пользовательским интерфейсом, я думаю, что «BackgroundWorker» будет лучшим выбором, если использовать 'async' и' await'. – Brian

+6

Неправильно. 'async' НЕ запускает никаких потоков, поэтому ваш DoSomeVeryExpensiveCalculation() НЕ будет выполнен в фоновом режиме. Помните, что 'async' предназначен для операций, которые выполняются только на короткое время, а затем' ждут 'что-то. Если вы хотите работать долго ресурсоемкие операции в фоновом режиме, вам нужно явно сделать это в фоновом режиме: статические задачи DoSomeVeryExpensiveCalculationAsync (объект арг) { возвращение Task.CreateNew (() => DoSomeVeryExpensiveCalculation (арг)); } – Daniel

9

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

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

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 может использоваться со всем, что будет завершено в какой-то момент в будущем. Вот кнопка расширения.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(); 

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

4

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

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

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

17

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

Однако есть потенциально другие сценарии, с которыми эта технология хорошо сочетается. Например, рассмотрим сценарий логики игры 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

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

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/

0

Здесь, вероятно, является хорошим примером того, как не использовать новую функцию асинхронной (что не пишет новый 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"); 
      } 
     } 
    } 
} 

результата:

Бегина производного

Начало Базы

Здравствуйте

Середины 1 Производного

End Base

Мира

EndDerived

С некоторой иерархией наследования (а именно с помощью команды шаблона) я желаю, чтобы делать такие вещи, как это время от времени.

2

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

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

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

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

Есть несколько типов параллелизма, где async является не лучший инструмент:

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

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

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

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

0

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