вторник, 22 июня 2010 г.

Асинхронный вызов

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

Как же именно можно осуществлять подобные вызовы? Мне пришло в голову следующее шаманство:
Создадим объект, в который будем складывать последовательно функции, которые необходимо вызвать, а по завершению коллбэка вызывать на исполнение следующую функцию.
Что нам понадобится?
  • Массив, чтобы в нём хранить функции
  • Добавление функции в массив
  • Исполнение текущей функции
  • Индикатор, для того, чтобы понимать, когда-же закончился коллбэк.
У меня получилось вот такое прелестное чудо:
function SyncSequence(){
  this.__functions__ = new Array();
}
SyncSequence.prototype={  
  add:function(func){
    this.__functions__.push(func);    
  },
  wrapCallback:function(callback){
    var $this = this;    
    return function(){
      callback.apply(null,arguments);
      $this.runSequence();
    }
  },
  runSequence:function(){    
    if(this.__functions__.length > 0){
      this.__functions__.shift()();
    }
  }
}
В данном случае __functions__ является очередью, в который мы последовательно помещаем те вызовы, которые должны использовать.
wrapCallback создаёт функцию, которая заменит традиционный коллбэк. Основное её отличие заключается в том, что в конце исполнения данного ей коллбэка будет вызвана следующая функция в очереди.
Ну а runSequence просто запускает верхушку на исполнение. Процесс создания очереди будет выглядеть примерно так.
var ss = new SyncSequence();
ss.add(function(){
  test(1,ss.wrapCallback(testCallback))
});
ss.add(function(){
  test(2,ss.wrapCallback(testCallback))
});
ss.runSequence();
Конечно, не образец изящества, но всё-таки. Дополним код полусферическим примером:
function test(arg,callback){
  alert("Test started " + arg);
  setTimeout(function(){
    callback(arg + 100);
  },1000);
}

function testCallback(arg){
  alert("Test callback started " + arg);
}
При компоновке и запуске мне последовательно выдались 4 алерта:
  • Test started 1
  • Test callback started 101
  • Test started 2
  • Test callback started 102
Ура. Конструкция может существовать не только в невесомости, но и в условиях слабой гравитации. Поразительно, но мне для осуществления коварных планов по порабощению мира написанию одной маленькой утилиты этого богатства вполне хватило.
Будет нужно, подумаю что ещё можно будет добавить. А пока оставлю как есть.
И да, асинхронные вызовы снова стали синхронными. Троекратное ура.

Как всегда, исходные коды можно скачать.

понедельник, 21 июня 2010 г.

Немного о динамических вызовах

Так сложилось, что иногда приходится писать код вида подобного вида:
if (action is SetPropertyAction)
{
   cond.AddRange(GenerateAction((SetPropertyAction)action));
}
else if (action is CommandAction)
{
   cond.AddRange(GenerateAction((CommandAction)action));
}
else if (action is FocusAction)
{
   cond.AddRange(GenerateAction((FocusAction)action));
}
else if (action is TransitionEffectAction)
{
   cond.AddRange(GenerateAction((TransitionEffectAction)action));
}
else if (action is NavigationAction)
{
   blockBody.AddRange(GenerateAction((NavigationAction)action));
}
В тех случаях, когда внести код GenerateAction в интерфейс было бы неверно или невозможно (например в качестве action может придти int), а разделение по типу необходимо возникает вопрос, каким же именно образом можно избавится от портянки?
Для начала создадим сферического коня в вакууме для последующих пыток:
internal abstract class A{}
internal class B : A{}
internal class C : A{}
internal class D : A{}

internal class Worker
{
  public int Foo(B c)
  {
    return 1;
  }
  public int Foo(C c)
  {
    return 2;
  }
  public int Foo(D c)
  {
    return 3;
  }
}
Задача проста: сделать функцию, принимающую экземпляр класса A в качестве параметра и вызывающая «правильный» метод класса Worker.
Вот она классическая портянка
public static int FooClassic(A a)
{
  if (a is B)
  {
    return worker.Foo((B)a);
  }
  else if (a is C)
  {
    return worker.Foo((C)a);
  }
  else if (a is D)
  {
    return worker.Foo((D)a);
  }
}
Замечательная вещь. Работает, но в случае если количество вариантов будет плодиться ужас лютый. А если имеется у данных классов появятся наследники для части из которых нужно создавать метод отличный от метода папочки…
Cтрашные вещи в Датском королевстве могут творится. Может быть можно сделать как-то по другому?

Что сразу приходит на ум: рефлекшен. Можно же просто получить нужный метод после чего его вызвать. Всё просто и логично. С небольшой натяжкой можно сказать что красиво.
public static int FooReflection(A a)
{
  return (int) worker.GetType().GetMethod("Foo", new[] { a.GetType() }).Invoke(worker, new[] { a });
}
Но есть одна небольшая проблема. Это долго. Заставим сферического коня немного поскакать.
const int repeatCount = 1000000;
var listObjects = new List<A> { new B(), new C(), new D() };
И дальше замер скорости кода:
for (var i = 0; i < repeatCount; i++)
{
  foreach (var obj in listObjects)
  {
    FooClassic(obj);
  }
}
Если FooClassic у меня выдаёт порядка 117 миллисекунд, то FooReflection уже тратит порядка 7 секунд. В некоторых местах ухудшение времени в 65 раз не является критичным (например, если метод за всё время вызывается пару десятков раз), но может быть можно побыстрее?
Что будет, если мы сразу станем запоминать, для какого типа был вызыван метод?
public static TResult InvokeMethod<T, TArg, TResult>(this T obj, string name, TArg arg)
{
  var key = new ComposedKey(obj, name, arg.GetType());
  Delegate value;

  if (!_methodsCache.TryGetValue(key,out value))
  {
    var parametr = Expression.Parameter(typeof (TArg), "x");
    var body = Expression.Call(Expression.Constant(obj), name, new Type[] {}, Expression.TypeAs(parametr, arg.GetType()));
    var expression = Expression.Lambda<Func<TArg, TResult>>(body, parametr);
    value = expression.Compile();
    _methodsCache[key] = value;
  }
  return ((Func<TArg, TResult>) value)(arg);
}
ComposedKey в данном случае ключ, который позвляет запоминать последовательность объектов. По имени свойства и типу апгумента можно создать функцию, вызывающую необходимый метод. В данном случае expression.Compile как раз создаёт такой метод. Теперь можно написать функцию:
public static int FooInvoker(A a)
{
  return worker.InvokeMethod<Worker, A, int>("Foo", a);
}
Такой подход позволяет ещё немного сократить время. И теперь выполнение занимает порядка 1 секунды. Небольшое, но улучшение.
В четвёртом фреймворке появился новый объект - dynamic, вызовы которого всегда происходят динамически и выбирается наиболее подходящий метод. В данном случае можно попробовать воспользоваться именно этим классом.
public static int FooDynamic(A a)
{
  return worker.Foo((dynamic) a);
}
Это самый короткий и элегантный вариант, но по времени он всё равно отстаёт от классического решения.
Результаты:
Имя методаСкорость в миллисекундах
Classic117
Reflection6994
Invoker1112
Dynamic907

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

Исходные коды можно скачать по ссылке