Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
Вместо того, чтобы постоянно создавать и удалять объекты достаточно создать один объект, а затем в нём же занимать и освобождать ресурсы.
Expose, а второй — Dispose?lock(context)
{
context.Expose();
persons = context.Persons.Where(p=>p.Age > minAge).ToList();
context.Dispose();
}
Самое очевидное, что можно сделать для синхронизации:
Реальных измерений производительности в описанных сценариях у меня нет
Суть в том, чтобы показать, что существует такой паттерн Exposable
А кто-нибудь, кроме вас, его признает?
Когда приходит запрос, проверяется по таймеру состояние объекта context и, если он не готов к работе, вызывается метод Expose, после чего включается таймер, например, на секунд 10. Если запросов больше не было, то спустя этот промежуток вызывается Dispose, иначе таймер продлевается.
по такой схеме вполне можно получить выигрыш в производительности.
Моя задача показать идею. Возможно, кто-то уже доходил до неё, но в других источниках её описания мне не встречалось.
И вас вот вообще не смущает, что DbContext — это UOW, и его нельзя разделять между разными клиентами?
На основании чего вы думаете, что создание и уничтожение DbContext — такая дорогая операция?
Это означает, что не «существует паттерн Exposable», а «я нашел что-то похожее на паттерн». Так вот, то, что вы пытаетесь выше показать для DbContext, называется object pooling, и реализуется не так. А то, что вы показываете во вью-моделях — это обычный инициализационный метод. И это два разных паттерна для решения разных задач.
В статье упоминается про это, но задача стоит в том, чтобы показать альтернативу UOW.
SaveChanges.Возможно, выбран не самый удачный пример, но он хорошо иллюстрирует концепцию совместного применения Expose и Dispose методов.
Даже если классический object pooling выглядит по-другому, то это не означает, что альтернативных реализаций быть не может.
Если Expose — обычный инициализационный метод, то Dispose — деинициализационный — всё просто :)
Хочу лишь спросить, не замечаете ли вы своего рода гармонии и красоты в существовании Expose и Dispose, конструктора и финализатора?
В том-то и дело, что Dispose — это не деинициализация, это полный отказ от объекта.
Так почему бы не расширить понимание паттерна Disposable?
Пусть метод приводит объект в дежурное состояние, когда он занимает меньше ресурсов, в спящий режим!
Чем вас не устраивает object pool, в котором все уже реализовано?
Потому что его семантика уже определена.
Для своего круга задач вполне устраивает.
Возможно, иногда стоит выходить за привычные границы, чтобы обнаружить что-то новое.
Если вы обнаружили что-то новое, используйте это новое, а не трогайте уже существующее. Вы правда не понимаете смысла понятия «паттерн»/«шаблон проектирования»?
Пускай есть объект, который достаточно дорог при создании и занимает ресурсы которые нужно переодически освобождать, а также важно его внутреннее состояние. То есть вариант с UOW и пулом не подходит, поскольку внутреннее состояние должно оставаться прежним.
Expose/Dispose, потому что один и тот же класс отвечает и за долгоживущие, и за короткоживущие данные. Что хуже, вы нарушаете привычное для пользователя поведение Dispose, при котором после вызова Dispose к объекту обращаться не надо.class UserDataProvider
{
public Guid UserId {get; set;}
public IDictionary<string,object> SessionData {get;private set;}
private SqlConnection _conn;
public void Expose()
{
_conn = new SqlConnection(..);
_conn.Open();
}
public void Dispose()
{
_conn.Dispose();
_conn = null;
}
public X GetSomeData()
{
var cmd = _conn.CreateCommand();
//...
}
}
Dispose?GetSomeData до вызова Expose?Expose, после первой строчки, будет эксепшн?interface IUserDataProvider: IDisposable
{
X GetSomeData()
}
class UserData
{
public Guid UserId {get; set;}
public IDictionary<string,object> SessionData {get;private set;}
public IUserDataProvider GetProvider()
{
return new UserDataProvider();
}
class UserDataProvider: IUserDataProvider
{
private readonly SqlConnection _conn;
private readonly UserData _userData;
public UserDataProvider(UserData userData)
{
_userData = userData;
_conn = new SqlConnection(..);
_conn.Open();
}
public void Dispose()
{
_conn.Dispose();
}
public X GetSomeData()
{
var cmd = _conn.CreateCommand();
//...
}
}
}
using, он гарантирует вызов DisposeGetSomeData до инициализации объекта невозможенЕсли внутри конструктора будет эксепшн, объект сразу попадет в очередь GC, где и будет радостно собран вместе со всеми его зависимостями (включая вызов финализатора, если тотопределен)
Вызов GetSomeData до инициализации объекта невозможен
internal class UserDataProvider : IExposable, IDisposable
{
public Guid UserId { get; set; }
public IDictionary<string, object> SessionData { get; private set; }
private SqlConnection _conn;
public bool HasConnection { get { return _conn != null; } }
public UserDataProvider()
{
Expose();
}
~UserDataProvider()
{
Dispose();
}
public void Expose()
{
if (HasConnection) return;
_conn = new SqlConnection();
_conn.Open();
}
public void Dispose()
{
if (!HasConnection) return;
_conn.Dispose();
_conn = null;
}
public X GetSomeData()
{
if (!HasConnection) Expose();
try
{
var cmd = _conn.CreateCommand();
//...
}
catch (TimeoutException exception)
{
_conn = null;
return GetSomeData();
}
}
}
Говорят, что эксепшены в конструкторах — это не очень хорошая практика.
Это столь же очевидно, как невозможность вызвова этого метода сразу после исполнения Dispose.
Какие минусы, на ваш взгляд, у такой реализации и чем она хуже классической?
Expose, на вызове _conn.Open, произойдет ошибка?Dispose не появилось — вы не можете обернуть вызов пары Expose/Dispose в using.GetSomeData незаметно для пользователя откроется соединение. А когда оно закроется? Никто не знает.Expose? Можно просто вызывать GetSomeData, поведение будет ровно таким же.Dispose в финализаторе, если вы не владеете неуправляемыми ресурсами.Зато теперь все стало еще смешнее. Теперь при вызове GetSomeData незаметно для пользователя откроется соединение. А когда оно закроется? Никто не знает.
Но ладно бы у вас было неявное состояние, это еще можно понять, но какой тогда смысл в методе Expose? Можно просто вызывать GetSomeData, поведение будет ровно таким же.
Собственно, один минус никуда не делся — что случится, если внутри Expose, на вызове _conn.Open, произойдет ошибка?
PPS Не надо при таймауте просто выкидвать коннекшн, вы его не освободили еще, и теперь он повиснет в пуле на некоторое неизвестное время.
Если вас эта строчка очень смущает, то можете её убрать.
GetSomeData до вызова Expose».Гарантировано Dispose вызовется, когда ссылок на объект не останется.
Есть ещё свойство HasConnection, поэтому при желании можно контролировать наличие соединения методами Expose/Dispose, или можно не контролировать если лень или это не критично.
Expose/GetSomeData и просто GetSomeData ничем не отличается. Второе даже лучше, потому что сеодинение захватывается попозже.В вашем же случае нужно оборачивать в try-catch весь блок using
Expose).Здесь имеется в виду превышение интервала бездействия по которому соединение закрылось автоматически.
TimeoutException бывает не только в таких случаях, и не всегда гарантировано, что соединение закрыто?На мой взгляд плюс реализации в том, что она неявно, но безопасно выполняет всю работу по установке соединения
… и мы вернемся к ситуации «что будет, если вызвать GetSomeData до вызова Expose».
А когда это произойдет для объекта, лежащего в сессии? Через полчаса после того, как пользователь покинет сайт? А сколько на сайте одновременных пользователей?
А смысл? Поведение между Expose/GetSomeData и просто GetSomeData ничем не отличается. Второе даже лучше, потому что сеодинение захватывается попозже.
Вы не понимаете, похоже. В моем случае не надо ничего оборачивать в try-catch (параноики могут обернуть вызов _conn.Open в конструкторе, но это не обязательно), потому что объект хоть и будет создан, но на него не будет ни одной ссылки, поэтому он будет сразу собран GC. А в вашем случае некорректно инициализированное соединение будет висеть внутри вашего объекта, и никакого способа избавиться от него нет (если, конечно, вы не реализуете try-catch внутри Expose).
А ничего, что TimeoutException бывает не только в таких случаях, и не всегда гарантировано, что соединение закрыто?
К сожалению, только неявно. Никакой безопасности в вашей реализации нет, более того, вы с каждой версией порождаете новые потенциальные утечки.
Это точно такая же ситуация, что и с вызовом метода после Dispose, и ничего в ней нет особенного.
using именно это и преследует: в нем нельзя вызвать объект ни до инициализации, ни после деинициализации.Нет ничего нерешаемого в данной ситуации, если это достаточно критично для приложения.
С помощью вызовов Expose/Dispose можно более точно контролировать время жизни соединения, если это нужно.
GetSomeData?Более того так очень легко проверить доступность сервера базы данных (протестировать соединение).
Как так не нужно оборачивать? Если у вас в конструкторе возникнет исключение, а вы его нигде не обработаете, то приложение упадёт с UnhandledException. Или вы что-то другое имеете в виду?
Не помню точного названия исключения, которое возникает при превышении интервала бездействия и автоматическом его разрыве сервером, но оно точно есть и отличается от остальных. Что-то вроде ConnectionHasBeenClosedByServer.
Давайте больше конкретики.
DisposeExpose)TimeoutExceptionМою реализацию вы можете использовать точно таким же образом с помощью using, как и свою.
using(var p = new UserDataProvider())
{
p.GetSomeData()
}
Если же нужно более тонкое управление поведением установки соединения, а порой возникает и такая необходимость,
ваша реализация не столь удобна.
какой-то сверхсложной логики тут нет, поэтому разберётся даже начинающий разработчик
private readonly UserData _userData;
public UserDataProvider(UserData userData)
{
_userData = userData;
Expose();
}
Я вас уже некоторое время прошу привести пример такой необходимости.
Извиняюсь, но не обратил внимание на параметр в кострукторе. Подправьте немного и покроется ваш вариант.
UserId и SessionData — в них же больше нет смысла, правильно? — и получаем мое решение. Напомню, что изначально вы ставили задачу следующим образом:Пускай есть объект, который достаточно дорог при создании и занимает ресурсы которые нужно переодически освобождать, а также важно его внутреннее состояние. То есть вариант с UOW и пулом не подходит, поскольку внутреннее состояние должно оставаться прежним. Совместное же использование Expose/Dispose решает эту проблему.
Да просто протестировать соединение с сервером (иногда бывает такая кнопка в некоторых приложениях).
Всё же не разделяю ваше мнение.
По крайней мере существует сценарий совместного использования Expose/Dispose
Например, дата-контракт сериализаторы, используемые для хранения состояния вью-моделей, не вызывают конструктора при создпании объекта
Собственно из этого и родился паттерн Exposable.
В данном случае речь идет не о мнении, а об аргументах.
Вы так и не смогли обосновать его необходимость.
Что такое объект?
Как вы решаете какие переменные и методы принадлежат одному объекту?
По-моему, по такой схеме вполне можно получить выигрыш в производительности. Не проверял это на практике, но исхожу из логических рассуждений.
При необходимости цикличных ссылок двух классов друг на друга, проще поддерживать шаблон медиатор.
Скажете, сильная связность, природа и миллионы лет эволюции плохой архитектор?
Изменяемость и подстраиваемость под внешние условия на очень высоком уровне.
По вашей логике — в данной системе высокая связность,
var productsViewModel = Store.Get<SettingsViewModel>();
Store.Get<ProductsViewModel>
Мне интересен реальный пример, оправдывающий такой overhead. Он у вас есть? Из проекта, из кода, чтобы без Exposable было сложнее, чем с ним.
Не забудьте сохранить состояние как логическое (вкладки, текст), так и визуальное (размеры и положения окна, настройки шрифта, цвета).
Exposable паттерн. Независимые инжекции путём экспанирования