2/27/2008 11:20:56 PM

Похоже, RTM для Windows 2008 Server нагрянул.

Из сегодняшнего от Майкрософт:

Today marks the official product launch of Windows 2008 and IIS7, a capstone to 5 years of engineering work to create the best Web server on the planet.

Tags:

2/24/2008 8:29:00 PM

Доработал сегодня Transaction Objects. Пофиксил старые баги, добавил новые :)

Из основных изменений:

  1. Объект теперь не надо явно регистрировать в транзакции.
    Точнее, вообще никак не надо.
    Сделал так потому, что для сложных объектов, которые "внутри" оперируют или содержат другие транзакционные объекты и т.д, явная регистрация всех этих "вложенных" транзакционных объектов в транзакции становится головной болью.
    Теперь всё, что делается внутри ObjectTransactionScope, автоматически относится только к текущей транзакции. Для TransactionObjects\TransactionProperties, разумеется :)
  2. TransactionProperties теперь обладают некоторыми метаданными.
    В частности, появилась возможность задать значение по умолчанию, возможность коррекции присваиваемого значения (CoerceValue) и возможность оповещения об изменениях свойства (PropertyChanged).
    Реализовано примерно так же, как и в случае с DependencyObjects - через коллбек-делегаты.

Так же могу отметить, что скорость работы изменилась в лучшую сторону :)

Transactions.zip (25.47 kb)

2/21/2008 7:48:00 PM

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

Вообще со всей этой чехардой выезда из Бельгии (тут тоже идет попутный процесс переговоров, связанных с переездом) и стремительно уменьшающимся количством времени как-то даже не тянет ничего делать. Даже эти самые транзакционные объекты не делаются, хотя до "беты" осталось минут 15-20 поковыряться.

Эх.

2/20/2008 8:28:05 PM

Scott Guthrie в своем блоге поделился новшествами и доработками, которые увидит .NET 3.5 в ближайшие месяцы.
Прочитать можно здесь: http://weblogs.asp.net/scottgu/archive/2008/02/19/net-3-5-client-product-roadmap.aspx

Там и про улучшение производительности WPF, и ускорение загрузки приложений, и еще много чего.

Особенно порадовала новость о том, что появится Ribbon. И DataGrid-контрол тоже будет не лишним.

Tags:

2/10/2008 9:57:00 PM

В субботу задумался, а как можно реализовать возможность объекта учавствовать в транзакциях? Чтобы можно было так же, как с БД: открыл транзакцию, понаизменял объект, потом решил: "не понравилось!" и откатил все изменения. А объект чтобы не пострадал.

Требования вырисовывались такие:

  1. До момента подтверждения транзакции все изменения, сделанные во время транзакции, должны быть видны только в рамках этой транзакции. За ее пределами объект должен выглядеть нетронутым.
  2. В случае вложенных транзакция (транзакция Б внутри транзакции А), если транзакция Б завершается, ее изменения становятся доступны для транзакции А, но по прежнему остаются невидимыми вне пределов транзакции А до ее фиксации.
  3. В случае отката транзакции объект остается в том же самом состоянии, как и до начала этой транзакции.
  4. Создавать такие объекты должно быть так же просто, как и "обычные" типы.

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

Сначала решил было использовать механизм контекстов .NET Framework. Вроде, указал атрибутом контекст объекту - а дальше все будет делаться само. После пары часов возни с контекстами от этой идеи решил отказаться по двум причинам. Первая в том, что там объект должен наследоваться от ContextBoundObject, при этом для него, понятно, создаются Real и Transparent прокси, плюс рефлексия, короче, производительность. Вторую причину я уже не помню, что-то там толи уж очень сложно получалось, толи не получалось вовсе, уже не суть.

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

// Using a DependencyProperty as the backing store for Name. This enables animation, styling, binding, etc...
public static readonly DependencyProperty NameProperty =
    DependencyProperty.Register("Name", typeof(string), typeof(MyClass), new UIPropertyMetadata(0));
 
public string Name
{
    get { return (string)GetValue(NameProperty); }
    set { SetValue(NameProperty, value); }
}

То, что надо! Разработчик только декларирует свойство, а то, какие уж там транзакции и как они учитывают ему уже не надо думать.

Получается примерно следующая конструкция:

public class Person : TransactionObject
{
    private static readonly TransactionProperty NameProperty
        = TransactionProperty.Register(typeof(Person), typeof(string));
 
    public string Name
    {
        get { return (string)base.GetValue(NameProperty); }
        set { base.SetValue(NameProperty, value); }
    }
}

Похоже, правда? :)
Наследуемся от TransactionObject (у Майкрософта DependencyObject) и регистрируем TransactionProperties (у Майкрософта DependencyProperties).

Далее, в .NET Framework есть такое пространство имен: System.Transactions, в котором уже реализованы классы Transaction, CommittableTransaction, TransactionScope и другие. В принципе, все это как раз и предназначено для поддержки транзакций, и было бы грех это не использовать. Правда, здесь меня ожидали два неприятных сюрприза: ни от одного из этих классов нельзя наследоваться - раз, и замечательный во всех отношениях TransactionScope ведет себя просто безобразно: когда объекту приходит команда Commit или Rollback, то совершенно невозможно определить, в рамках какой транзакции были вызваны эти самые Commit и Rollback.

Короче, пришлось делать собственный класс: ObjectTransactionScope, лишенный этого недостатка.
В результате использование транзакции для объекта типа Person выглядит следующим образом:

1. Person user = new Person();
2. user.Name = "Greg Johns";
3. Console.WriteLine("Initial value: " + user.Name);
4. Console.WriteLine();
5.  
6. using (ObjectTransactionScope tran1 = new ObjectTransactionScope(user))
7. {
8.     user.Name = "Henry Smith";
9.     Console.WriteLine("In transaction #1:\t" + user.Name);
10.     using (ObjectTransactionScope tran2 = new ObjectTransactionScope(user, TransactionScopeOption.RequiresNew))
11.     {
12.         user.Name = "Walter Simpson";
13.         Console.WriteLine("In transaction #2:\t" + user.Name);
14.         Console.WriteLine("\t(Transaction #2 is not committed)");
15.     }
16.     Console.WriteLine("In transaction #1:\t" + user.Name);
17.     Console.WriteLine("\t(Transaction #1 commit)");
18.     tran1.Complete();
19. }
20.  
21. Console.WriteLine();
22. Console.WriteLine("Finally:\t" + user.Name);

Здесь сначала мы работаем с экземпляром безо всяких транзакций (строки 2,3), затем открываем первую транзакцию и меняем имя пользователя (8). Если в этот момент кто-то обратится из другого потока к этому же объекту и прочитает свойство Name, он должен увидеть Greg Johns, а не Henry Smith, так как транзакция tran1 еще не была зафиксирована. Хотя "изнутри" этой транзакции будет "виден" именно Генри Смит (строка 9).

В строке 10 мы открываем новую транзакцию для объекта, причем требуем, чтобы была создана именно новая транзакция.
Снова меняем имя на Walter Simpson, поведение то же самое - изменение видно только внутри транзакции tran2.

В строке 15 транзакция tran2 заканчивается и, так как она не была подтверждена, происходит "откат" всех изменений (точнее сказать, изменения не фиксируются), поэтому после выхода из транзакции tran2 внутри tran1 мы продолжим видеть все того же Генри Смита.

Я знаком с Генри, он хороший парень, поэтому транзакцию tran1 фиксируем (строка 18).
Соответственно, после выхода из транзакции tran1 (строка 22) мы, уже вне всяких транзакций, увидим, что имя пользователя было изменено на Henry Smith. И все остальные эти изменения тоже увидят.

Кажется, достаточно просто и, главное, стандартно: регистрация свойств и обычное поведение из System.Transactions.

На реализацию механизма ушли суббота и воскресенье (если честно, то часа по 3-4 в день), прототип готов :)

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

А пока код проекта - в аттачменте.
Вопросы, предложения, баги и т.д. - в комменты или на почту ;)

Transactions.zip (48.77 kb)

2/6/2008 9:50:21 PM

Вот в комментариях к прошлому постингу был интересный вопрос о том, что не оставить ли все "заморочки" с многопоточностью на совести программиста?
Действительно, с одной стороны мысль разумная. С другой же стороны речь идет именно о гарантиях. Ну, как пример возьмем тот же самый объект:

public sealed class TestObject    
{    
    public IEnumerable<string> Users { get; set; }
}

Какого рода действия здесь может предпринять программист для того, чтобы чувствовать себя уверенно в многопоточной среде?
Самое простое и часто распространенное - синхронизация. При этом, понятное дело, нужно синхронизировать как операции чтения, так и операции записи. То есть, если коллекцию кто-то пишет, то ее нельзя читать. А если коллекцию кто-то читает, то ее нельзя писать. Тогда случае самого примитивного lock'а мы сводим "на нет" всю нашу многопоточность: одновременно с коллекцией может работать только один поток, тот, который захватил этот самый монитор (lock).

Можно придумать более хитроумный подход, когда мы позволяем читать коллекцию многопоточно, а блокировать коллекцию будем только в случае ее изменения. Естественно, до начала внесения изменений все, кто читал коллекцию должны завершить свои операции (те, кто еще не начал - ждут), затем мы вносим изменения, а затем снова разрешаем операции чтения.
Подобные механизмы обычно реализуются через две очереди, одна содержит операции чтения и задачи из нее выполняются во многих потоках, вторая - операции записи, задачи из которой выполняются эксклюзивно. Такой подход легко реализуется через, скажем, CCR, а так же специальная инфраструктура для этого имеется в свободно распространяемой библиотеке Wintellect Power Threads, написанной Джефри Рихтером.
Однако и в случае этого подхода нас ожидает ряд сюрпризов. Один из них - продолжительность операции чтения коллекции. Представьте, что енумерируя TestObject.Users я для каждого элемента коллекции делаю запрос к веб-сервису. Допустим, я получаю данные по пользователю, обрабатываю их, вношу изменения в соответствующие статистические данные, делаю биллинг и т.д. Операции-то небыстрые. Я енумерирую, а все остальные операции просто стоят и ждут. Потому, что следующая на очереди - операция изменения коллекции. Она ждет, пока я закончу читать, остальные операции чтения ждут, пока операция записи выполнит свою работу эксклюзивно. Семеро одного ждут.
Да, я могу вместо енумерирования Users каждый раз создавать на его основе новую коллекцию, или лист, и потом тормозить с ней столько времени, сколько хочу, не мешая другим. Но - я вынужден буду делать это каждый раз, когда я хочу работать с коллекцией Users. Чтобы не заставлять ждать других, я каждый раз при чтении создаю новую копию коллекции. Плюс мне, человеку, в общем-то постороннему, нужно знать и помнить, что работать надо так и только так. Плюс - достаточно сложная инфраструктура с задачами, очередями... Точнее, минусы все это, в общем-то ;)

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

2/2/2008 12:01:00 AM

Недавно я писал о преимуществах работы с IEnumerable в сравнении со всякими листами и массивами, сегодня немного продолжу эту же тему.

Мысль навеяна сниппетом "invoke" из Visual Studio, который (совершенно правильно) предлагает возбуждать событие вот таким вот образом:

 

public event EventHandler StateChanged;
private void OnStateChanged()
{
    EventHandler changedHandler = StateChanged;
    if (changedHandler != null)
    {
        changedHandler(this, EventArgs.Empty);
    }
}

 

вместо "привычного" и часто встречающегося:

 

public event EventHandler StateChanged;
private void OnStateChanged()
{
    if (StateChanged != null)
    {
        StateChanged(this, EventArgs.Empty);
    }
}

 

Вся "фишка" здесь, понятное дело, в многопоточности: во втором случае подписчик (в другом потоке) может отписаться от события как раз в тот момент, когда проверка на null уже прошла, но перед тем, как будет произведена попытка возбудить событие. Соответственно, второй случай - потенциальный кандидат на NullReferenceException, к тому же трудно воспроизводимый. В то же время в случае первого примера возникновение такого исключения невозможно.

Теперь вернемся к коллекциям и посмотрим на них с этой стороны.
Допустим, у нас есть какой-либо объект, который "выставляет наружу" коллекцию (сейчас нам не важно, какого типа, массив ли, IEnumerable ли, пусть это будет List<> для примера):

 

public sealed class TestObject
{
    public List<string> Users { get; set; }
 
    public void AddUser(string user)
    {
        Users.Add(user);
    }
}

 

Предположим, что есть некий код, работающий с экземпляром этого объекта. Допустим, что этот код зачем-то перебирает список пользователей, который содержится в объекте, с помощью foreach, такое часто бывает. А в это время, в другом потоке, приходит команда на добавление нового пользователя в список с помощью, например, метода AddUser(...). В этом случае мы немедленно получаем исключение в том коде, который занимался перебором значений. Исключение будет связано с тем, что коллекция была изменена и дальнейший перебор ее невозможен. Кстати, узнать из исключения кто, где и зачем поменял коллекцию будет невозможно, что тоже не сахар в смысле отладки.

Хорошо, будем перебирать коллекцию не с помощью foreach, а с помощью for, в этом случае от такого рода исключения мы еще как-то застрахуемся:

 

for (int i=0;i<to.Users.Count;i++)
{
    Console.WriteLine(to.Users[i]);
    Thread.Sleep(500);
}

 

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

В общем, думаю, проблема понятна. Что с решением?

В общем виде решение, очевидно, нужно искать в неизменяемости значений, то, что называется Immutable object.
Immutable object не может изменить своего значения после того, как он единожды был инициализирован. Пример - тип String в .NET. К слову, в .NET нет возможности объявить объект константным (неизменяемым), к большому сожалению. Поэтому приходится "выкручиваться" на предмет immutable objects по-всякому. Об этом, возможно, в следующий раз.

Что может дать такая неизменяемость? На самом деле очень многое. Такие объекты можно без опасений использовать из нескольких потоков, не беспокоясь о том, что их состояние вдруг изменится, не переживая по поводу "грязного чтения" (это когда в одном потоке вы уже успели изменить 5 свойств объекта, а еще 15 на очереди, когда кто-то начинает работать с тем же объектом в другом потоке, получая, по сути, невалидный объект) не заморачиваясь излишней синхронизацией... На самом деле, представьте, сколько придется писать кода для синхронизации потоков, всяческих lock'ов, для того, чтобы вышеуказанный простой пример стал потокобезопасным, надежным? А ведь надо еще не запутаться в этих локах, не допустить мертвых блокировок.. А если что-то посложнее?

Получается, что для того, чтобы решить нашу задачу, мы должны соблюсти два условия:

Первое. Использовать неизменяемые коллекции. При этом возвращение из свойства "стандартной" ReadOnlyCollection<> (в виде "return m_Users.AsReadOnly();")  нам, в общем случае, не подойдет, так как она не имеет своего енумератора и является всего лишь оберткой над List<>, изменив который мы получим все те же проблемы со "сломанным" енумератором. Массив нам не подойдет тоже, так как позволяет изменять элементы используя индексатор.
Получается, что придется делать собственную реализацию, скажем, ReadOnlyList<T> : IList<T>, который не позволит изменять коллекцию вообще.

Второе. Вытекает из первого. Так, как мы собираемся хранить в нашем TestObject не List<T>, а ReadOnlyList<T>, то при любой необходимости изменения коллекции мы просто создаем новый ReadOnlyList на основе имеющегося+все необходимые изменения, а потом просто подменяем им существующий. Подмена ссылки - операция атомарная, поэтому "грязного чтения" не возникнет.

Соблюдя эти два простых условия мы получим следующую простую ситуацию: те, кто уже получил ссылку на коллекцию из свойства Users, так и продолжат работать с ней, с той, которая была валидна на момент обращения к свойству, даже если само свойство Users и поменяет свое значение и станет возвращать уже другую коллекцию. И никто не умрёт :)
За исключением, однако, случая из последнего примера (цикл for). Здесь ошибки все еще возможны, так как обращение к свойству Users производится дважды (на каждую итерацию): в момент проверки условия и в момент обращения к элементу. Между двумя этими моментами Users может поменять свое значение - и "привет".
От этого последнего неудобства можно избавиться аж двумя способами:

  1. Тем же способом, как и в случае с событием, о котором я говорил в самом начале: сначала получить ссылку на экземпляр, а потом уже делать с ним все, что хочешь и енумерировать сколь угодно долго любыми циклами. Однако, это неудобно: не заставишь же разработчиков не использовать некоторых конструкций. Поэтому, смотрим пукнт 2.
  2. Возвращать IEnumerable<> :) Во-первых это и означает получение ссылки и дальнейшую работу только с ней, а во-вторых, for'ом по такому свойству пройтись просто не получится :) Если кому-то и нужно использовать именно конструкцию for, или просто нужен массив, или лист, то, опять же, создать его на основе IEnumerable<> не представляет никакой сложности. И, главное, это не скажется на работе других частей приложения.

Меньше локов, товарищи ;)

Powered by BlogEngine.NET 1.6.0.0

About the author

Alexey Raga Alexey Raga
.NET software developer.

E-mail me Send mail

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2010

Sign in