Анонимные итераторы в C#?

Сегодня мы поиграем с новыми фичами C# 5.0 из состава Async CTP, а конкретно с новыми трансформациями на уровне компилятора для поддержки ключевых слов async/await (скорее всего в релизе эти ключевые слова будут другими, общественность не радостно встретила такой выбор). Тут лежит спека с подробным описанием нововведений, однако на данный момент компилятор C# не сильно ей следует.

По своей сути фича очень и очень простая, а необходимые для её реализации элементы имеются в компиляторе ещё начиная с версии C# 2.0 - это трансформации, которые компилятор делает для yield return-итераторов. Трансформация заключается в разбиение метода-итератора на набор состояний по точкам вызова yeild return/yield break, а затем компилирование класса, реализующего IEnumerable<T> (или IEnumerator<T> и не обобщённые версии обоих) с большим switch по состояниям исходного метода в реализации MoveNext(). Очень хорошо и подробно про имплементацию итераторов в Microsoft’ском компиляторе C# пишет Jon Skeet здесь.

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

Сегодня мы решим обратную задачу - сделаем из async-методов блоки yield-итераторов! А так как в качестве async-методов могут выступать лямбда-выражения, то мы можем получить анонимные итераторы в C# (yield return в лямбда выражениях запрещён):

Func<string, Task<int>> f = async url =>
{
    var web = new System.Net.WebClient();
    var page = await web.DownloadStringTaskAsync(url);
    Console.WriteLine(page);
};

Итак, приступим:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;

public static class Iterator
{

Определим вложенный класс-awaiter (пользователь вовсе не должен замечать этот класс, он необходим для инфраструктуры C# async):

public abstract class Awaiter<T>
{
    public Awaiter<T> GetAwaiter() { return this; }
    public abstract bool BeginAwait(Action next);
    public abstract void EndAwait();
}

Согласно спецификации, любое выражение под await должно обладать экземплярным методом (или extension-методом) с именем GetAwaiter, возвращающее значение типа, в котором определены методы BeginAwait и EndAwait. Первый из них должен иметь параметр типа System.Action и возвращать bool-значение, второй - не иметь параметров и возвращать значение любого типа или void.

Смысл всего этого добра очень прост - когда вы ожидаете с помощью await какое-либо выражение, то у этого выражение вызывается метод GetAwaiter() и у возвращённого значения вызывается BeginAwait, при этом туда передаётся некий Action-делегат. Внутри себя BeginAwait как-либо запускает асинхронную операцию, а в качестве callback’а использует переданный Action-делегат. Если запуск асинхронной операции произошёл успешно, то BeginAwait возвращает true и исполнение async-метода прерывается (управление возвращается коду, вызвавшему async-метод). Позже, когда асинхронная операция завершится, она вызывает в качестве callback’а Action-делегат, который на самом деле вызывает продолжение исполнения async-метода с момента последнего await'а. При этом у последнего awaiter-класса вызывается метод EndAwait, который может вернуть результат асинхронной операции (как в примере выше). Помимо всего этого, BeginAwait может вернуть false и тогда выполнение async-метода продолжится синхронно (например, если операция выполнилась очень быстро и не потребовала асинхронности), с последующим вызовом EndAwait для получения результата.

В нашей реализации тип значения под await-выражением и класс-awaiter являются одним и тем же типом, поэтому GetAwaiter просто делает return this.

Далее определим тип делегата, возвращающий описанный нами класс-awaiter, им будет удобнее пользоваться в дальнейшем, чем Func<T, Awaiter<T> >:

public delegate Awaiter<T> Yield<T>(T value);

Главный метод из public surface получает Action-делегат (который должен являться async-методом) с единственным параметром типа делегата Yeild<T>:

public static IEnumerable<T> Of<T>(Action<Yield<T>> @async)
{
    if (@async == null)
        throw new ArgumentNullException("async");

    return new IteratorAwaiter<T>(@async);
}

Теперь самое сложное, реализация класса IteratorAwaiter<T>:

sealed class IteratorAwaiter<T>
    : Awaiter<T>, IEnumerator<T>, IEnumerable<T>
{
    readonly Action<Yield<T>> @async;
    readonly int initialThreadId;
    Action moveNext;
    T currentValue;

    public IteratorAwaiter(Action<Yield<T>> @async)
    {
        this.@async = @async;
        this.initialThreadId =
            Thread.CurrentThread.ManagedThreadId;
        this.moveNext = InitialMoveNext;
    }

Класс сохраняет в поле Action-делегат из async-метода и идентификатор текущего потока (это нужно для тех же целей, что и в итераторах). При этом поле moveNext изначально указывает на метод InitialMoveNext, который запускает async-метод и в качестве делегата Yield<T> передаёт лямбда-выражение, устанавливающее значение полю currentValue и возвращающее класс IteratorAwaiter<T> инфраструктуре async в качестве Awaiter<T>:

void InitialMoveNext()
{
    this.moveNext = null;
    this.@async(value => {
        this.currentValue = value;
        return this;
    });
}

Данный код решает проблему того, что async-методы в C# не являются отложенными - код до первого await всегда вызывается синхронно, а вот исполнение yield return-итераторов всегда отложено до первого вызова MoveNext. Поэтому, чтобы из async-метода сделать итератор, надо отложить вызов @async до первого вызова MoveNext.

Теперь реализация Awaiter<T>, которая просто сохраняет делегат продолжения в то же поле moveNext и обнуляет его при продолжении работы async-метода (вызов EndAwait):

public override bool BeginAwait(Action next)
{
    this.moveNext = next;
    return true;
}

public override void EndAwait()
{
    this.moveNext = null;
}

Реализация IEnumerator<T> раскрывает все секреты:

public T Current
{
    get { return this.currentValue; }
}

object IEnumerator.Current
{
    get { return this.currentValue; }
}

public bool MoveNext()
{
    if (this.moveNext == null) return false;
    this.moveNext();
    return (this.moveNext != null);
}

public void Reset() { }
public void Dispose() { }

Знаток итераторов тут же заметит некорректную реализацию Dispose, однако я пока отложу обсуждение данной проблемы. Интерес представляет метод MoveNext, который вызывает делегат из поля moveNext, и проверяет это же поле после вызова на null. Дело в том, что если в async-методе не останется await'ов, то последний вызов EndAwait установит поле moveNext в null и итератор должен будет сообщить, что он “закончился”.

Наконец, реализация IEnumerable<T>, которая создаёт копию IteratorAwaiter<T> если запрашивают ещё один IEnumerator<T> из другого потока или когда этот экземпляр уже хоть раз использовали для перебора (именно поэтому InitialMoveNext первым делом обнуляет поле moveNext) - это необходимо для поддержки оптимизации, при которой IEnumerable<T> и IEnumerator<T> являются одним и тем же экземпляром, так же как в итераторах C#:

public IEnumerator<T> GetEnumerator()
{
    if (Thread.CurrentThread.ManagedThreadId
                        != this.initialThreadId
        || this.moveNext == null
        || this.moveNext.Target != this)
    {
        return new IteratorAwaiter<T>(@async);
    }

    return this;
}

IEnumerator IEnumerable.GetEnumerator()
{
    return GetEnumerator();
}

Вот и всё, полный исходный код доступен здесь. Понимаю, выглядит это всё жестоко, но если есть желание поглубже разобраться со внутренностями Async CTP, то очень советую побегать по данному коду отладчиком.

Теперь мы можем определять итераторы в виде лямбда-выражений и это даже не особо страшно выглядит (к сожалению, необходима явная аннотация типа итератора):

var xs = Iterator.Of<int>(async yield =>
{
    await yield(100);
    await yield(200);

    for (int i = 0; i < 10; i++)
    {
        await yield(i);

        if (i % 6 == 0)
            return; // вместо yield break
    }
});

foreach (var x in xs) Console.WriteLine(x);

Обратите внимание, что всё лямбда-выражение приводится к типу делегата Action<T>, не имеющему возвращаемого значения, при этом вызов return начинает играть роль yield break.

Стоит отметить, что делегат yield-параметра можно вызвать где угодно по коду, но смысл итератором будут возвращаться только значения, передаваемые под await-выражением. Можно было бы предусмотреть буфер и позволить итератору энергично наполнять его последовательными вызовами yield, а потом последовательно отдавать буфер при следующем вызове await.

По производительности данный итератор лишь в 1.5-2 раза медленнее обычного yield return, из-за дополнительных вызовов через делегаты и некоторого оверхэда на инфраструктуру async. К сожелению, требуется сборка AsyncCtpLibrary.dll из состава Async CTP, хотя возможно подменить её на свою, реализовав небольшой функционал.

Ещё одно отличие async-методов от итераторов - возможность делать await внутри try-catch (это запрещено в итераторах):

async static void CatchIteratorImpl(Iterator.Yield<string> yield)
{
    try
    {
        await yield("indise try");
        throw new Exception();
    }
    catch   { Console.WriteLine("=> catch"); }
    finally { Console.WriteLine("=> finally"); }
}

static void Main(string[] args)
{
    Iterator
        .Of<string>(CatchIteratorImpl)
        .Materialize()
        .Run(Console.WriteLine);
}

В примере я использую методы из Reactive Extensions for .NET (Run - это просто foreach с телом из переданного делегата, Materialize позволяет увидеть момент завершения последовательности), получаем вывод:

OnNext(indise try)
=> catch
=> finally
OnCompleted()

Это всё хорошо, а теперь о плохом - данная реализация не может корректно обрабатывать ситуации, когда пользователь итератора сам прекратит перебор и запросит у итератора Dispose. Если к данному моменту исполнение итератора C# было внутри try-finally (в итераторах C# они разрешены), то будет выполнен finally-блок, тогда как в случае наших итераторов из async-методов код finally выполнен не будет:

var xs = Iterator.Of<int>(async yield =>
{
    try
    {
        await yield(1);
        await yield(2);
        await yield(3);
    }
    finally { Console.WriteLine("=> finally"); }
});

xs.Take(2) // <== останавливаем перебор итератора
  .Materialize()
  .Run(Console.WriteLine);

Вывод:

OnNext(1)
OnNext(2)
OnCompleted()

В случае итераторов:

static IEnumerable<int> YieldFinally()
{
    try
    {
        yield return 1;
        yield return 2;
        yield return 3;
    }
    finally { Console.WriteLine("=> finally"); }
}

static void Main(string[] args)
{
    YieldFinally()
        .Take(2)
        .Materialize()
        .Run(Console.WriteLine);
}

Получаем:

OnNext(1)
OnNext(2)
=> finally
OnCompleted()

Ещё одна плохая новость в том, что исправить это вовсе не представляется возможным, так как компилятор C# из Async CTP просто не генерирует для async-методов необходимый код, рассчитанный на такое поведение (грубо говоря, нельзя заDispose'ить асинхронный метод во время await'а). Можно защитить пользователя от таких ситуаций, бросая исключение, если Dispose вызывают до окончания перебора итератора (к сожалению, данный код не защищает от вызова Dispose итератом самому себе):

public void Dispose()
{
    if (this.moveNext != null)
        throw new InvalidOperationException(
            "Early disposing is not supported.");
}

Если в вашем итераторе нету try-finally или using, то реализация совсем ничем не отличается от обычного итератора C# 2.0. А так как async-методы в виде лямбда-выражений допускают вложенность, то можно издеваться над мозгом сколько угодно вложенными друг в друга итераторами:

Iterator.Of<int>(async yield =>
{
    foreach (var x in
        Iterator.Of<int>(async y =>
        {
            await y(1);
            await y(2);
            await y(3);
        }))
    {
        await yield(x + 1);
        await yield(x + 2);
    }
})
.Run(Console.WriteLine);

Таким образом, мы обнаружили очень большую схожесть между трансформациями async-методов из Async CTP и давно имеющимися в C# yield return-итераторами, что позволяет выражать одну фичу через другую. Естественно, данная реализация приведена только в ознакомительных целях и серъёзного применения не имеет (из-за описанных выше проблем с finally).

Final APIs of BDB for supporting async access

stream_state const*

ostream(size_t stream_size);

Initiate a stream_state for write. Underlying DB allocates a chunk of size ‘stream_size’ at least. Write operations follow this method will be written into the chunk.

初始化一個寫入資料用的 stream_sate。底層的 DB 根據 stream_size 配置一個夠大的 chunk,後續的寫入動作會將資料寫入該 chunk。

stream_sate const*

ostream(size_t stream_size, AddrType addr, size_t off=npos);

Initiate a stream_state for write. Underlying DB allocates a chunk of size ‘stream_size’ + old data size w.r.t. the ‘addr’, migrate data to the chunk, and preserve a ‘empty room’ of size ‘stream_size’ that distant ‘off’ bytes from begin of the chunk. Write operations follow this method will be written into the room.

初始化一個寫入資料用的 stream_sate。底層的 DB 根據 stream_size 、由 addr 取得的舊資料大小,配置一個新 chunk,將舊資料搬到該 chunk,並保留一個大小為 stream_size ,距離 chunk 頂端 ‘off’ bytes 的空間。後續的寫入動作會將資料寫入該空間。

stream_sate const*

istream(size_t stream_size, AddrType addr, size_t off=0);

Initiate a stream_state for read a specific block of a chunk. The block is defined by those three parameters.

stream_sate const*

stream_write(stream_sate const* state, char const* data, size_t size);

Write data to a chunk/room according to the ‘state’. If any errors occur, this method abort all modifications associated with the state and returns NULL ptr.

stream_sate const*

stream_read(stream_sate const* state, char* output, size_t size);

Read data from a chunk/block according to the ‘state’. If any errors occur, this method abort all modifications associated with the state and returns NULL ptr.

void

stream_abort(stream_sate const* state);

Client can explicitly abort all modifications associated to the state through this method. Any internal errors cause the DB abort all modification has been made implicitly.

script 태그의 defer와 async 속성

일반적으로 script 태그가 다운로드되고, 파싱하는 과정에서는 document이 파싱이 차단되는데, 이를 피하는 방법으로 defer와 async속성을 사용할 수 있다.

 

두가지 속성은 script가 다운로드되는 동안 document 파싱을 차단하지 않는 점은 동일하지만, 다운로드 완료후 스크립트를 파싱하는 타이밍에 차이가 있다.

 

defer는 onload직전에 파싱. 

async는 networking이 끝나면 곧바로 파싱. 

 

따라서 async는 처리순서가 중요하지 않은 script에만 조심스레 사용해야하며, 역시나 최선의 방법은 모든 script 태그를 <body> 최하단에 선언하는 것이다.

 

[참고자료]

greenlet/gevent

Тут будет немного сумбурных мыслей по поводу greenlet’ов в контексте gevent.

Гринлеты это хак

Немного пояснений о том как же работают гринлеты и к чему это все может привести.

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

Поскольку питон - интерпретируемый язык и если бы у него был управляемый стек (стек програмы на питоне был бы реализован сам интерпретатором), то greenlet’ы делаются элементарно - заводим два стека и все.

А поскольку стек как раз не управляется питоном, то greenlet начинает шаманить с системным стеком. А именно:

1. При переключении гринлетов (из А в Б) вычисляет размер использованого стека относительно вышестоящего стек-фрейма
2. Копирует данную область из стека в кучу (делая malloc/realloc)
3. Переписывает данные из кучи в стек для гринлета Б
4. Патчит регистры процессора, что бы указатель стека был на правильном месте
5. Освобождает память в куче для гринлета Б (free)
6. Патчит PyThreadState (переменные recursion_depth, top_frame) что бы питон не сломался
7. Возвращает управление в интерпретатор питона

И того, на одно переключение гринлета происходит 2 memcpy, возможно один realloc и один free. Зачем free? Потому что при алгоритмах с рекурсией размер стека может быть большим. Например - 50 КБ. Соответственно если после переключения оставлять копию стека в куче - получим 2х кратное увеличение использования памяти.

Какие общие проблемы у такого подхода:

1. Проблемы со сборкой мусора

Из официальных док: Greenlets do not participate in garbage collection; cycles involving data that is present in a greenlet’s frames will not be detected. Storing references to other greenlets cyclically may lead to leaks.

Что означает - любые переменные которые были выгружены гринлетами в кучу из стека не будут принимать участия в сборке. А поскольку у питона обычный reference count, то и все объекты на которые они ссылаются тоже подчищены не будут. 

Так что прийдется очень внимательно писать код, а то потом прийдется ловить ошибки на продакшене.

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

3. Магия для интерпретатора

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

Гринлеты занимаются магией и влезают в стек. Если произойдет исключение будет очень трудно посмотреть реальный стек-трейс.

Если произойдет исключение в гринлете, оно передается на вышестоящий гринлет. Если произошло еще одно исключение в вышестоящем гринлете, то исходное исключение потеряется. А все потому, что питон работает категориями потоков (PyThreadState на поток), а у нас кучка виртуальных потоков в одном реальном. К слову, из одного гринлета можно читать исключение которое произошло в другом гринлете.

Реальный размер используемого системного стека может быть значительно больше чем значение PyThreadState.recursion_depth - при слоеном пироге из фреймов разных гринлетов. Чем грозит - не знаю, памяти сейчас много, но может себя вести странно на “нестандартных” (не х86) архитектурах.

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

Вот тут еще есть немного: http://code.google.com/p/coev/wiki/GreenletProblems

Ну а теперь о хорошем.

Мне нравится концепция асинхронного программирования без callback’ов. Код становится сильно проще и красивее.

gevent, в целом - симпатичный. Ну да, нужно патчить все что можно, что бы библиотека питона стала асинхронной. Некрасиво, но других вариантов нет.

Например взять ту же SQLAlchemy - ее заставили работать асинхронно под gevent. Ага, monkey patching, но какие есть альтернативы, учитывая что это самая вменяемая ORM и она, к сожалению, не умеет работать асинхронно из коробки?

Что плохо - основа на которой написан gevent меня пугает, поскольку мне страшно ставить ее на высоконагруженный продакшн. Слишком много магии - очень плохо. Хотя вроде spotify что-то там писал на gevent, может библиотека уже production ready.

Ну а какие есть альтернативы?

Торнадо, да. Но появляется требование работы с базой данных и привет. Без баз данных - магии нет, код проще, но callback’и.

Такие вот дела.

In the end it works, but imag­ine what hap­pens if callback-based APIs become pop­u­lar and every jar you use with a call­back in its API has to have its own thread pool. Kind of sucks. That’s prob­a­bly why Netty punts on the issue. Too hard to make pol­icy deci­sions about this in a low-level net­work­ing library.

/cc Waffle, who said some of this before.

API客户端的异步接口改造

最近在维护一个API客户端,根据开发人员的需求,对接口做了些改造。 比如原先是

String getXXX()

的API,改为

Future<String> getXXX()

这里的Future跟java.util.concurrent.Future不太一样,主要是juc的Future缺乏异步结果监听,只能判断isDone()后get()获取结果,或者直接调用get()阻塞等待结果。

本Future参考netty3的接口,可以调用addCallback(cb)来增加结果的回调句柄,从使用角度来说,使用者可以用阻塞的风格调用:

String result = getXXX().get()

也可以

getXXX().addCallback(new Callback() { 
  public void onResult(String result) { ... } 
  public void onException(Exception e) {...} 
});

选择更加灵活,而且比起 getXXX(para1,para2,callback) 这样固定是异步风格的调用更好一些。如果需要同时调用好几个api,并针对结果做汇总处理,可以

future1 = getA() 
future2 = getB() 
.... 
result1 = future1.get() 
... 
calc(result1, result2, result3)

这样也就最多等待一个请求的时间,而不是N个。

后来碰到一个问题,有一些api,不能返回所需的值,只能返回id,再通过调用另一个api,因此这个客户端接口需要调用两次api才能提供返回值。在使用这种异步风格下,我可以通过创建2个callback,一个调用api2,一个将接收到的最终结果设置给返回给用户的Future,减少api调用阻塞的时间。

最后是api调用,它是用http rest风格请求,一般用apache httpclient是阻塞风格的,需要加入一个线程池来模拟实现这个异步接口,这里用netty实现了一个简单的http client,能够结合这套客户端,通过少数几个io worker线程来处理callback的执行,经过测试,效率还是不错的。除非callback中有db操作之类阻塞请求,否则CPU个数的io worker线程已经足够处理。

Kiev ALT.NET: Звіт про зустріч, присвячену Async, Rx та LINQ

Звіт про зустріч.

Асинхронное программирование в .NET

Сергей Тепляков

Rx – Reactive Extensions

Dmitry Pasko

LINQ Providers

Vitaly Baum

4й слайд - мій улюблений.

Як завжди дякуємо за організаційну допомогу Ciklum.

PotLite.js は、めんどうになりがちな非同期処理をとにかく楽にコーディングできるよう
直感的に記述できる Deferred オブジェクトを中核として実装しています。
そして、ユーザー (UI) への配慮を目的として
CPU など負荷のかからないループ処理やイテレータが利用できる JavaScript ライブラリです。
Blocking and non-blocking I/O

A theoretical look at non-blocking and blocking I/O

What is blocking I/O on the web?

Blocking I/O is code that causes processing to stop until it returns. Consider this Rails code:

class User < ActiveRecord::Base

  after_create :send_welcome_email

  def send_welcome_email
    Notifier.welcome_email(self).deliver
  end

end

Simple enough - a new user gets a welcome email after signing up. This method is blocking - processing stops until the mail is queued and we are returned from the Notifier class. This is standard practice in a Rails app and generally is fine for sending single emails.

When this can be a problem

Consider an example where we have a collaborative application sending emails to users when other users comment on something. Let’s say for arguments sake we have 50 separate emails to send (in the real world I know this is unlikely). If we use similar code to the example above when someone posts a comment processing will be blocked until all 50 emails have been sent.

So our app can be slowed by:

  • The time it takes to process 50 emails
  • A slow network to the mail server
  • Heavy load on the mail server

Until we return from this the slowness is passed directly to the user.

Increasingly web applications do not exist on a single server. They are becoming complex networked applications using cloud infrastructures, third-party APIs and services across the internet. In these kind of applications the potential for blocking operations like our small email example becomes bigger. In a world where speed matters we have to be increasingly creative about ways to avoid blocking operations.

Ways to solve this

In the case of the email example in Rails we can use something like resque_mailer to perform these tasks asynchronously. We bring up resque workers as separate processes and use Redis to store bits of data that the workers can use to do their work. It is a great library and very easy to use. Instead of forcing the user (and the execution of our application code) to wait until mail is sent we put the task onto a queue and get a worker to execute it.

In Rails this means using the following

  • Redis
  • Background processes
  • Monit, bluepill or god to monitor the background process

That’s a lot of extra stuff so the question is - why are we blocking on this at all?

Non-blocking I/O

Here’s some example code from nodemailer, a library for sending email via node.js

var nodemailer = require('nodemailer');

nodemailer.SMTP = {
    host: 'smtp.example.com'
}

nodemailer.send_mail(
    {
      sender: 'me@example.com',
      to:'you@example.com',
      subject:'Hello!',
      html: '<p><b>Hi,</b> how are you doing?</p>',
      body:'Hi, how are you doing?'
    },
    function(error, success){
      console.log('Message ' + success ? 'sent' : 'failed');
    }
);

In this case sending mail is non-blocking. The results of the function are passed to the callback function which acts accordingly. The callback can call another function, throw an error, or write to a log. But crucially this doesn’t block the execution of other parts of the application. This is really good as by design we are saying nothing is allowed to block so we don’t need Redis, background processes and tools to monitor the background processes. Furthermore by using this philosophy we can create complex networked applications without needing to worry about one node in the network slowing the application to a halt.

Non-blocking I/O is liberating in the sense that we don’t need to worry about anything blocking. Some argue that this approach leads to spaghetti code and can be difficult to debug. I don’t yet have an opinion on this but in terms of a design philosophy and knowing how things work on the web I really like the approach of saying ‘nothing should block’. What’s your view?

Проблемы асинхронных интерфейсов

image

Не секрет, что некоторые операции в программном обеспечении могут быть тяжеловесными. Например, операция может выполняться 10 мин. Или 30 секунд. Или даже 5. Все они похожи тем, что на время их исполнения в случае синхронной обработки блокируется пользовательский интерфейс - окно перестает быть отзывчивым, элементы управления ни на что не реагируют, а приложение становится похожим на мертвеца. Все дело в очереди обработки сообщений Windows и способе запуска этих тяжелых операций.

Логичным решением этой проблемы является разделение потока пользовательского интерфейса (UI thread) и потока для выполнения бизнес-задач - в этом случае очередь обработки сообщений окна работает вполне спокойно. В то время, пока наша задача отрабатывается, в основном окне мы видим какую-нибудь стандратную анимацию со словами “Подождите, сейчас все будет…”. И, казалось бы, проблема решена раз и навсегда: в тех местах приложения, где есть подозрение на чрезмерно долгую операцию создаем новый поток и обрабатываем все там. Microsoft, например, далеко пошел с этой стратегией - сначала изобрел APM, потом TPL и Rx, а в 5-ой версии C# и вовсе присутствует асинхронность на уровне языка. Даже в Windows 8 на уровне ОС, в недрах WinRT использует асинхронность.

Все бы ничего и кажется, что проблема с тяжелыми операциями решена. Если бы не одно “но”. Многие разработчики, понимая что в их арсенале есть асинхронность как-то не особо заботятся о быстродействии своих приложений. Если какая-то операция в приложении работает, например, 10 секунд, то первая реакция разработчика - “добавим асинхронности”. И всё. Между тем вся эта анимация с просьбой подождать и мнимая отзывчивость приложения - это не более чем иллюзия, визуальный обман. Сохраняя отзывчивость приложения мы не даем пользователю то, чего он ожидает - бизнес-ценности, результата работы этой операции. Мы лишь просим его подождать. А отзывчивость приложения сглаживает время ожидания. И если операция время от времени занимает 1-2 сек., то проблем нет. Но если каждая вторая операция требует по 10 сек. на выполнение, мигая при этом сообщениями с просьбой подождать, то это уже может напрягать пользователя.

Яркой демонстрацией такого поведения могут быть многие приложения для Windows Phone 7 - дождаться “подвисания” пользовательского интерфейса в этих приложениях вряд ли возможно. Однако, часто анимация “пожалуйста подождите” крутится достаточно долго. И операции выполняются долго. И все ощущение ”отзывчивости” приложений растворяется в воздухе как мыльный пузырь. При этом приложения WP7 - далеко не единственный пример, можно привести еще ряд похожих случаев. Но это уже не особо важно для понимания общей картины.

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

NFSでの書き込みが遅い

Twitterによると2010/12/12あたりにNFSの書き込みが遅いことが判明。

なんかNFSのReadは普通だけど、Writeが遅いなぁ。転送自体が遅いわけじゃなくて、転送しかかりが遅いっぽいのがまた良く分からん。ddでファイ ル作って測ってみると100BASEで9MB/sくらい出るにゃ出るんだが、tarから49個のファイルを展開するだけで8分もかかったりする
11:41 AM Dec 12th, 2010 P3:PeraPeraPrvから

うーん…。NFSへの書き込みが遅いのはサーバー側がBtrFSでフォーマットされてるせいかと思ってたけど、ext3でも結構遅いなぁ。まぁそれでも ext3とBtrFSでエクスポートされてるものを比べると、圧倒的にBtrFSベースの方が遅いんだけど。何がいけないんだ?
11:45 AM Feb 24th P3:PeraPeraPrvから

不良セクタを撲滅したおかげか、クライアントをopenSUSE 11.4にアップグレードしたおかげか分からんけど、なんかNFSが早くなってるなぁ。でも速度が ローカル>>ext4ベースNFS>>BtrFSベースNFS とそれぞれ圧倒的な速度差で、BtrFS頑張れ
1:35 PM Apr 2nd P3:PeraPeraPrvから

ずっと何がいけないんだろう何がいけないんだろうと思っていたけど、今日やっと原因が分かった。BtrFSとかサーバーのファイルシステムが悪いんじゃなかった。疑ってごめんねBtrFS。100Baseなのがいけないのかとか思ってたけどそれも違った。

じゃあ何が悪かったかというと、/etc/exportsのオプションである。「openSUSE NFS 遅い」で検索したら一発目に出てきた。なぜこんなに簡単に出てくるものが探せなかったのか…。

LunaTear: NFSが遅い(2)

結局サーバ側で/etc/exportfsでsyncになっていたところをasyncに変更だけで全て解決しました。

ちなみに、出ていた症状は次のようなもの

  1. 特定マシンからの書き込み速度が全く出ない(時間的に通常の8倍ぐらいの遅さ)
  2. 書き込み速度が全体的にわずかに(1割強)遅い
  3. 複数マシンから同時に書き込みを行うとasyncのマシンに比べて倍以上遅い

考えてみれば1番以外はsyncの影響だと容易に想像がつくし、1番もnfsstatで異様にrpcのretransが多かったので書き込みとタイミングが合わなくてパケットが捨てられたと解釈すれば納得できないことも無い感じ

ちゃんとオプション見て設定してたはずだったのに…。

ともあれ、45個程度のtarballを展開するのに30分くらいかかっていたものが、0.5秒程度とちゃんと実用できるような速度になってくれました。良かった。

2014-04-14のJS: Chrome35β、JavaScript非同期処理、spy-js

JSer.info #170 - Chrome 35 Betaがリリースされました。 Stableに全部入るかわかりませんがChrome 35βでは、ES6 Promises、WeakMapやWeakSet、Angular2.0でも使うとしてるObject.observe等が入ったり色々新しいものが入っています。

また、前回紹介した開発者ツールに非同期処理のスタックトレースをちゃんと追跡出来る仕組み等、大きな機能追加が多いバージョンとなりそうです。

Asynchronous JavaScript Interfacesというスライドでは、JavaScriptの非同期処理について書かれています。

特にChrome 35βでも入ってる、ES6 Promisesについてよく書かれています。 medikoo/plain-promiseというPromisesの実装をしながらどのような動きになってるかや、ES6 Promisesにdoneがないのは何故か?等詳しく書かれています。

WebStorm 8.0.1がリリースされましたが、8.xから入ったspy-jsについて Spy-js: WebStorm secret serviceという記事で詳しい機能解説がされています。

簡単に紹介するとspy-jsはproxyとして機能するJavaScriptのコードの実行時間やイベントのトレースを行えるツールです。(元々は独立したツールだったがJetBrainsが買収して統合された)

例えば、ボタンをクリックした時にJavaScriptのコードのどの行が実行されて、どの行が実行されてないか、それぞれの処理に何msかかったか、どのような順番で実行されたか、関数が返した値等をトレースすることが出来ます。

どのような仕組みでやっているかというと、proxyを通ったコードは一度JavaScript ASTにパースされて、instrument.jsという所でそれぞれのステートメントに対して計測用の関数をラップして、escodegenでJavaScriptのコードに戻して実行するという事をしています。

この仕組みは、power-assertでJavaScriptのテストをする ブラウザ編 | Web scratchで紹介している、 power-assertでも似たような事を行っています。

コードを変換して実行時にassertのエラー情報をより詳細に取れるようにするためにやっていて、他にもIstanbulのようなコードカバレッジツールなどで似たような手法が使われてると思います。

このような今まではよくわからない技術(独自の)に見えるところも、既にあるツールやライブラリの組み合わせでできてる事などが見えると色々と面白いなと思いました。

ヘッドライン

WebStorm 8.0.1 Bug Fix Update is Available: node-webkit, TypeScript 1.0 and Sass 3.3 | JetBrains WebStorm Blog

blog.jetbrains.com/webstorm/2014/04/webstorm-8-0-1-bug-fix-update/

WebStormReleaseNotenode.jsTypeScript

WebStorm 8.0.1リリース。

TypeScript 1.0、Sass3.3対応、node-webkitのデバッグ実行に対応

Chromium Blog: Chrome 35 Beta: More developer control over touch input, new JavaScript features, and unprefixed Shadow DOM

blog.chromium.org/2014/04/chrome-35-beta-more-developer-control.html

Chrome

Chrome35βリリース。

35で入るCSSのtouch-actionプロパティ、ES6 Promises、WeakMap/WeakSet、Object.observe、WebAudio、Shadow DOMのprefixがなし、CSS Font Loading

ESLint 0.5.0 released - ESLint

eslint.org/blog/2014/04/eslint-0.5.0-released/

JavaScriptToolsReleaseNote

ESLint 0.5.0リリース

eslint-env又はCLIの--envオプションで実行環境の設定が出来るように、グローバル変数を定義するCLI --globalオプションの追加等

YUI 3.16.0 Released - YUI Blog

www.yuiblog.com/blog/2014/04/10/yui-3-16-0-released/

YUIReleaseNote

YUI 3.16.0リリース。

Getfirebug Blog » Blog Archive » Firebug 2.0 beta 1

blog.getfirebug.com/2014/04/11/firebug-2-0-beta-1/

FirebugReleaseNote

Firebug 2.0 beta 1リリース。

ブレークポイント周りの強化。

コンソールパネルからエラーに対してブレークポイントを貼れるようになる等

Release Release 1.3.2 · bower/bower

github.com/bower/bower/releases/tag/v1.3.2

JavaScriptToolsReleaseNote

Bower 1.3.2リリース。

concurrency実行した時の問題を修正等

Express 4 — Javascript and the server — Medium

medium.com/javascript-and-the-server/aa6992b52bcd

node.jsReleaseNote

Express 4がリリースされた

アーティクル

power-assertでJavaScriptのテストをする ブラウザ編 | Web scratch

efcl.info/2014/0411/res3820/

JavaScripttestingbrowserbrowserify

power-assertを使ったブラウザでのテストについて。

testemとgulpを使った方法、browserifyとkarmaを使った方法、sourcemapでのデバッグについて書かれてる。

Spy-js: WebStorm secret service | JetBrains WebStorm Blog

blog.jetbrains.com/webstorm/2014/04/spy-js-webstorm-secret-service/

JavaScriptdebugToolsWebStorm

WebStorm8に入ったspy-jsの詳細。

トレースを行うproxyサーバを立てて、JSのトレースを行う。

イベントのスタックを見たり、カバレッジのようにコードの実行された部分の可視化や実行時の値の確認等が出来る。

Using WAI-ARIA in HTML の日本語訳を公開しました | IMAGEDRIVE

blog.imagedrive.jp/web/2014-04-09/using-wai-aria-in-html-ja

WAI-ARIA翻訳

"Using WAI-ARIA in HTML"の翻訳

JavaScript - ナウでヤングな CSS Font Loading - Qiita

qiita.com/damele0n/items/6afc5160cf7ea8b15787

JavaScriptwebfonts

WebFontsのロードタイミングを取得できたりするCSS Font Loadingについて。

document.fontsの使い方について書かれてる。

スライド、動画関係

meet.js - kwiecień 2014 | Events Poznań Blog

events.pozoga.eu/meet-js/

JavaScriptイベント動画

meet.jsの動画が公開された。

メモリ管理、gulp、WebComponents、Selenium Builderの話等

Javascript 101

themouette.github.io/slides-clermontjs-js101/#/

JavaScriptスライド

JavaScriptの基本からnpmやGrunt等のツールやリソースについて幅広く書かれてるスライド

Introduction to d3.js

ireneros.com/conf/nicar/introduction-to-d3.html#1

d3.jsスライド

d3.jsについてのスライド。

セレクタ、スタイルの適応、scale、データの操作、アニメーション等について

Running Node.js apps in production

fhemberger.github.io/talks/nodejs-in-production/#/cover

node.jsToolsスライド

Node.jsアプリのデプロイ、モニタリング等についてのスライド。

recluster, PM2, kibana, new relic、エラーレポート等ツールについて色々まとまってる

Asynchronous JavaScript Interfaces

medikoo.com/asynchronous-javascript-interfaces/promises-deferred/

JavaScriptスライド

JavaScriptの非同期処理についてのスライド。Event、コールバック、Generator、Promises。

Promisesのエラーハンドリング、簡単に実装しながら学ぶ、donethenの違い、Deferred、doneの論争についてよくまとまってる

サイト、サービス

#DevTools Tip 検索 - Google+

plus.google.com/s/%23DevTools%20Tip

ChromedebugToolsTips

Chrome DevToolsのTips集

Relato - Open Source Statistics

bripkens.github.io/relato/

node.jswebservice

npmに登録されてるパッケージの統計データ。

ユーザー数、利用者(dependencies)、開発利用者(devDependencies)、ページランク等でソートして一覧出来る (元なるデータがちょっと更新されてないので若干古いかも)

ソフトウェア、ツール、ライブラリ関係

Famous/famous

github.com/famous/famous

HTML5JavaScriptlibraryUImobile

リッチウェブアプリ向けのJavaScriptフレームワーク。

Event管理、物理エンジン、数学ライブラリ、transform、transition、Canvasのコンテナ、View Layout等を持ってる。

UIのパフォーマンスとメンテンスビリティに重きがある感じ

書籍関係

Understanding… by Nicholas C. Zakas [Leanpub PDF/iPad/Kindle]

leanpub.com/understandinges6

ECMAScriptbook

Nicholas C. ZakasによるES6についての書籍

Backbone.js Patterns and Best Practices | Packt Publishing

www.packtpub.com/backbone-js-patterns-and-best-practices/book

backbone.jsbook

Backbone.jsのベストプラクティスとパターンについての書籍

https://github.com/nzakas/understandinges6

Promise Pattern

Introduction

Promise is a software development pattern that is used to deal with deferred result in concurrent development. The key philosophy behind promise pattern is non-blocking logic that runs asynchronously over blocking logic so that the application can have better efficiency particularly in user interaction. Also promise makes programming against the asynchronous operation result easier. Promise happens a lot in the real world. For example, when an interview is arranged on Thursday 9:00 AM (makes a promise), the interview will not happen until the interviewee actually shows up (fulfils the promise). And the interviewer will continue have his normal job and life until the interviewee shows on the appointed time. This scenario in promise might have a programming like this, Promise (interview.arranged).then(interview.startedIfIntervieweeShowsAndTimeIs9AM() or interview.cancelledIfIntervieweeNotShow()).

Description

Name:

Promise – also known as Future, Deferred

Intent:

Provides a mechanism to schedule a work to be done on a value or event that has not yet been computed or happened (Microosft, 2011).

Problem :

A typical challenge of asynchronous programming is when there are several concurrent asynchronous operations; it’s difficult to make a future operation against the results from these operations.

Context:

When there are several related asynchronous operations happen concurrently, handling the result from those concurrent operations are very difficult, and the nest callbacks or other complex programming model may increase massively of the complexity of the application. Besides all these will be tightly coupled with each other, and nothing can be really reused.

When you want to simplify the programming within this situation, particularly when want to decrease the complexity and decouple the data handling.

Forces  The increased complexity of the application may make the application code difficult to be understood and make the maintenance cost high.

Solution:

A solution to solve this is to introduce a deferred object (promise) to be returned immediately after calling an asynchronous operation. The callbacks can be attached to this deferred object and called when the promise is actually satisfied or failed.

In concrete form, the promise pattern can be presented as

Promise.when( operations ).then(fulfilled_handler, error_hanlder, progress_hanlder) (Zyp, n.d.)

Resulting context:

The complexity of handling results from the concurrent asynchronous programming is reduced.

A simple sample

Although the ATM case may possibly to apply promise patter, there is no real need to use it from my point of view. The synchronous programming model is totally fine to the ATM and no need introduce any complexity. So I would like to take another example from jQuery (a JavaScript library focusing on DOM level operation and extension).

Since jQuery 1.5, a new object Deferred was introduced which is essential an implementation of promises pattern.

Below is a simple example of jQuery Deferred object when makes two separated Ajax calls to different resources,

$.when($.ajax(‘/api/banksystem1’, $.ajax(‘/api/banksystem2’)).then(the_functon_for_success, the_function_for_failure);

The biggest benefit from above programming sample is that the two Ajax calls are made in the same time but the then() operation is only executed when both of them are successfully executed or either of them fails.

Conclusion

Promise pattern is very useful to simplify the asynchronous programming in many scenarios, particularly in the areas that require non-blocking operations, like touch screen of mobile devices and tablets. Promise is a high level abstraction to react against the asynchronous operations’ result. It provides better encapsulation by making a promise.

References:

Zyp, K. (n.d.), ‘Promises/A’ [online]. Available from: http://wiki.commonjs.org/wiki/Promises/A (Accessed: 13 November, 2011).

jQuery.com (n.d.), ‘jQuery.when’ [online]. Available from: http://api.jquery.com/jQuery.when/ (Accessed: 13 November, 2011).

Text
Photo
Quote
Link
Chat
Audio
Video