GuiDispatcher.AddPeriodicalAction
Atom
10.01.2013


Подскажите пожалуйста - как работает GuiDispatcher.AddPeriodicalAction? Судя из названия метода - действие вызывается периодически - но с какой частотой. Пример использования встречается в SampleGui, где с помощью этого метода обновляется информация в открытых стаканах. Логика там, как я понимаю, следующая - ведется два синхронизированных словаря ключ-значение: один - инструмент/окно стакана, другой - рыночный стакан/окно стакана.


private readonly SynchronizedDictionary<Security, QuotesWindow> _quotesWindows = new SynchronizedDictionary<Security, QuotesWindow>();
private readonly SynchronizedDictionary<MarketDepth, QuotesWindow> _changedDepths = new SynchronizedDictionary<MarketDepth, QuotesWindow>();

Далее происходит подписка на обновление стаканов trader.MarketDepthsChanged += TraderOnMarketDepthsChanged, и вот совсем мне непонятно, что делается в TraderOnMarketDepthsChanged:

private void TraderOnMarketDepthsChanged(IEnumerable<MarketDepth> depths)
{
foreach (var depth in depths)
{
var wnd = _quotesWindows.TryGetValue(depth.Security);

if (wnd != null)
_changedDepths[depth] = wnd;
}
}

Что тут происходит, как я понимаю: рассматриваем каждый обновившийся стакан и ищем соответствующее ему окно стакана в _quotesWindows:
var wnd = _quotesWindows.TryGetValue(depth.Security);

Далее что происходит в этой строчке: _changedDepths[depth] = wnd; ? Откуда мы знаем, что в словаре _changedDepths есть элемент с ключом depth? И зачем на каждом обновлении стакана depth мы переприсваиваем значение wnd в словаре? Ну и самый главные вопрос - как потом в wnd появляются новые котировки с обновленного depth? Как работает .AddPeriodicalAction?

Просто я думал логика должна была быть примерно такая - достаточно держать лишь один словарь - _quotesWindows и при получении обновленного MarketDepth - найти соответствующий ему элемент в _quotesWindows и обновить _quotesWindows.Value. Разве не так?

Я еще только начинаю разбираться в программировании такого уровня, так что сильно не бейте и спасибо за помощь.



Спасибо:


Геннадий Ванин (Gennady Vanin)

Фотография
Дата: 11.01.2013
Ответить


FlashPlayer Перейти

Код
private readonly SynchronizedDictionary<Security, QuotesWindow> _quotesWindows = 
     new SynchronizedDictionary<Security, QuotesWindow>();

private readonly SynchronizedDictionary<MarketDepth, QuotesWindow> _changedDepths = 
     new SynchronizedDictionary<MarketDepth, QuotesWindow>();


Просто я думал логика должна была быть примерно такая - достаточно держать лишь один словарь - _quotesWindows и при получении обновленного MarketDepth - найти соответствующий ему элемент в _quotesWindows и обновить _quotesWindows.Value. Разве не так?


Или другими словами можно было бы исключить
Код
private readonly SynchronizedDictionary<Security, QuotesWindow> _quotesWindows = new SynchronizedDictionary<Security, QuotesWindow>(); 


т.к. между экземпляром инструмента (Security) и экземпляром стакана (MarketDepth) существует однозначная связь, а security - экземпляр Security определяется, как из экземпляра стакана depth (MarketDepth), так и экз-ра котировок стакана, привязанного к окну QuotesWindow (ObservableCollection<Quote>)

Но, в наст. реализации, _quotesWindows - это экземпляр окна котировок, содержащее коллекцию котировок, не содержащие внутри себя идентификацию инструмента (стакана) или стакана (инструмента) этих котировок:
Код

public partial class QuotesWindow
{
    public QuotesWindow()
    {
        Quotes = new ObservableCollection<Quote>();
	InitializeComponent();
     }

     public ObservableCollection<Quote> Quotes { get; private set; }
}  


QuotesWindow.xaml

Зато для этого есть словари, из которых по экземпляру инструментов (Security) или стаканов (MarketDepth) можно извлечь экземпляр коллекции котировок - QuotesWindow, т.е. ObservableCollection<Quote>

Насколько я понимаю, есть другие необходимости
  • обычно для одного и того же инструмента (в терминалах и программах S#) можно открывать несколько окон одного и того же стакана, для каждого со своим клоном-копией (данных) стакана.

    В общем, неочевидно, что нужны твкие усложнения, при том, что теряется гибкость и структурность + см. следующее соображение

  • В рассматриваемом приложении, данные инструментов копируются-дублируются во много окон
    - SecuritiesWindows, котировки-стакана (каждого инструмента) QuotesWindow и др. (MyTradesWindows, OrdersWindow, PositionsWindows и др.) . показываются в разных окнах с копиями данных, некоторые из которых задублированные копии одних и тех же сущностей (например - инструмент)
    Поэтому делать из экземпляров объектов инструмента синглтоны - неоправданно усложнять, при этом теряя в гибкости


FlashPlayer Перейти

Код
private readonly SynchronizedDictionary<Security, QuotesWindow> _quotesWindows = 
     new SynchronizedDictionary<Security, QuotesWindow>();

private readonly SynchronizedDictionary<MarketDepth, QuotesWindow> _changedDepths = 
     new SynchronizedDictionary<MarketDepth, QuotesWindow>();


Далее происходит подписка на обновление стаканов trader.MarketDepthsChanged += TraderOnMarketDepthsChanged, и вот совсем мне непонятно, что делается в TraderOnMarketDepthsChanged:

Код

private void TraderOnMarketDepthsChanged(IEnumerable<MarketDepth> depths)
{
    foreach (var depth in depths)
    {
        var wnd = _quotesWindows.TryGetValue(depth.Security);

        if (wnd != null)
              _changedDepths[depth] = wnd;
     }
}

Что тут происходит, как я понимаю: рассматриваем каждый обновившийся стакан и ищем соответствующее ему окно стакана в _quotesWindows:
var wnd = _quotesWindows.TryGetValue(depth.Security);

Насколько я понимаю, котировки дублируются в 2х словарях и при их обновлении их надо скопировать (синхронизировать) из одного в другой

FlashPlayer Перейти
Далее что происходит в этой строчке: _changedDepths[depth] = wnd; ? Откуда мы знаем, что в словаре _changedDepths есть элемент с ключом depth?

Ну, так они для того и называются синхронизированными

Их вначале инициализируют "одинаково" (т.е. синхронизованно) по (структурам данных) выбранным инструментов (каждой из которых соответствует стакан, вернее структура данных стакана), обновления котрых нас интересуют, а потом их танцуют

Детали можно посмотреть с помощью ReSharper
Спасибо:

FlashPlayer

Фотография
Дата: 11.01.2013
Ответить


Огромное спасибо за такой развернутый ответ. Все практически встало на свои места. Остался один вопрос по теме - как все таки работает "синхронизация" мд этими двумя словарями? Вот мы их проинициализировали - два словаря с одинаковыми значениями, но разными ключами. Неужели этого достаточно для того, чтобы если в одном словаре появился новый элемент, то он появился и в другом? Я честно гуглил,но так и не понял как работает этот класс.

И есть еще один вопрос, который связан с моими непониманием некоторых особенностей языка. Помогите пожалуйста также наглядно разобраться с объектом класса GuiDispatcher : _dispatcher. Вот в том же примере в том же классе SecuritiesWindow встречается такой код:

Код

_dispatcher.AddPeriodicalAction(() =>
			{
				foreach (var pair in _changedDepths.SyncGet(s => s.CopyAndClear()))
				{
					pair.Value.Quotes.Clear();
					pair.Value.Quotes.AddRange(pair.Key.SyncGet(md => md.Clone().Reverse()));
				}
			});


Как я понимаю, что тут происходит: AddPeriodicalAction, как следует из названия, вызывает с какой-то (с какой???) периодичностью нижеидущее действие, которое, в свою очередь, заполняет QuotesWindow из MarketDepth. Как же все таки истинно работает этот диспатчер - в чем его обязанность, удобство перед таймером или вообще необходимость?

Ведь теперь получается совершенно запутанная последовательность действия, как я понимаю. Вот у нас есть два словаря, пока что пустых. Пользователь кликает по кнопке создать стакан и в словарь _quotesWindows добавляется элемент А = (Security, QuotesWindow). По идее, следуя из того, что словари синхронизированы (хотя я до сих пор не понимаю, как это осуществляется, где и кем) в словаре _changedDepths появляется элемент В = (MarketDepth, QuotesWindow). Причем А и В имеют одно и тоже значение,а ключи разные, хотя и соответствуют одному и тому же инструменту. Далее в TraderOnMarketDepthsChanged по каждому обновленному MarketDepth делается что-то невероятное: ищется соответствующий ему элемент в _quotesWindows и соответствующий ему элемент в _changedDepths, после этого QuotesWindow из первого присваивается второму. Зачем?!Вот этот момент мне не понятен. Далее, как раз с помощью диспатчера (что мне тоже непонятно, но выше я уже это спросил) в _changedDepths во всех элементах берутся котировки из ключа и пихаются в значение. Таким образом мы наконец-то получаем обновленный QuotesWindow (то есть в стакане нашем, наконец-то появляются обновленные котировки). Но это же адово сложный процесс.

Ведь можно, как вы сказали выше, вести только один словарь _changedDepths, и в событии TraderOnMarketDepthsChanged делать то, что делается в _dispatcher.AddPeriodicalAction и результат по идее дб тот же. Или нет? Спасибо заранее за пояснения. А то оч уж сложная механика, для вроде кажущихся несложный действий.
Спасибо:

FlashPlayer

Фотография
Дата: 11.01.2013
Ответить


Геннадий Ванин (Gennady Vanin) Перейти
[quote=FlashPlayer;23289]

Насколько я понимаю, котировки дублируются в 2х словарях и при их обновлении их надо скопировать (синхронизировать) из одного в другой



Вот тут тоже спорно и непонятно, ведь именно в TraderOnMarketDepthsChanged никакого обновления ни одной котировки не происходит - тут то и беда. Оно происходит только в диспатчере этом. И вот такая запутанность непонятна. В TraderOnMarketDepthsChanged только копирование по ссылке значений происходит (опять же непонятно зачем). Короче совсем каша какая-то.
Спасибо:

Moadip

Фотография
Дата: 11.01.2013
Ответить


Цитата:
Как я понимаю, что тут происходит: AddPeriodicalAction, как следует из названия, вызывает с какой-то (с какой???) периодичностью нижеидущее действие, которое, в свою очередь, заполняет QuotesWindow из MarketDepth. Как же все таки истинно работает этот диспатчер - в чем его обязанность, удобство перед таймером или вообще необходимость?

Библиотека Ecng.Xaml не обфусцирована.
И когда возникают вопросы как же оно там все внутри устроено, можно открыть и посмотреть код Reflector или R#. Как бесплатная альтернатива dotPeek.

Открываем, смотрим:
Код

    public void AddPeriodicalAction(Action action)
    {
      if (action == null)
        throw new ArgumentNullException("action");
      this._periodicalActions.Add(action);
      this.StartTimer();
    }


Переходим по StartTimer:
Код

    private void StartTimer()
    {
      this._lastTime = DateTime.Now;
      lock (this._lock)
      {
        if (this._timer != null)
          return;
        this._timer = new DispatcherTimer(DispatcherPriority.Normal, this.Dispatcher);
        this._timer.Tick += new EventHandler(this.OnTimerTick);
        this._timer.Interval = new TimeSpan(this.Interval.Ticks / 10L);
        this._timer.Start();
      }
    }

Видим создание объекта типа DispatcherTimer. Полное имя - System.Windows.Threading.DispatcherTimer, что это за класс можно почитать на MSDN.
Дальше интересна строчка:
Код

this._timer.Interval = new TimeSpan(this.Interval.Ticks / 10L);

Точнее this.Interval.Ticks. Переходим по Interval и видим следующее:
Код

    public TimeSpan Interval
    {
      get
      {
        return this._interval;
      }
      set
      {
        if (value <= TimeSpan.Zero)
          throw new ArgumentOutOfRangeException("value");
        this._interval = value;
        this.StopTimer();
        this.StartTimer();
      }
    }

И последний переход по _interval, в то место где данное поле инициализируется:
Код

private TimeSpan _interval = TimeSpan.FromMilliseconds(100.0);


В итоге по умолчания AddPeriodicalAction работает с периодичностью = TimeSpan.FromMilliseconds(100.0).Ticks / 10L.


Спасибо:

FlashPlayer

Фотография
Дата: 12.01.2013
Ответить


Хм, спасибо снова за такие пояснения. Не все понятно в середине, но важен итог - получается AddPeriodicalAction вызывается каждые 10мс? Тогда остается открытой эта часть моего вопроса:

"
Ведь теперь получается совершенно запутанная последовательность действия, как я понимаю. Вот у нас есть два словаря, пока что пустых. Пользователь кликает по кнопке создать стакан и в словарь _quotesWindows добавляется элемент А = (Security, QuotesWindow). По идее, следуя из того, что словари синхронизированы (хотя я до сих пор не понимаю, как это осуществляется, где и кем) в словаре _changedDepths появляется элемент В = (MarketDepth, QuotesWindow). Причем А и В имеют одно и тоже значение,а ключи разные, хотя и соответствуют одному и тому же инструменту. Далее в TraderOnMarketDepthsChanged по каждому обновленному MarketDepth делается что-то невероятное: ищется соответствующий ему элемент в _quotesWindows и соответствующий ему элемент в _changedDepths, после этого QuotesWindow из первого присваивается второму. Зачем?!Вот этот момент мне не понятен. Далее, как раз с помощью диспатчера (что мне тоже непонятно, но выше я уже это спросил) в _changedDepths во всех элементах берутся котировки из ключа и пихаются в значение. Таким образом мы наконец-то получаем обновленный QuotesWindow (то есть в стакане нашем, наконец-то появляются обновленные котировки). Но это же адово сложный процесс.

Ведь можно, как вы сказали выше, вести только один словарь _changedDepths, и в событии TraderOnMarketDepthsChanged делать то, что делается в _dispatcher.AddPeriodicalAction и результат по идее дб тот же. Или нет? Спасибо заранее за пояснения. А то оч уж сложная механика, для вроде кажущихся несложный действий.
"

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

Геннадий Ванин (Gennady Vanin)

Фотография
Дата: 12.01.2013
Ответить


FlashPlayer Перейти
Ведь теперь получается совершенно запутанная последовательность действия, как я понимаю. Вот у нас есть два словаря, пока что пустых. Пользователь кликает по кнопке создать стакан и в словарь _quotesWindows добавляется элемент А = (Security, QuotesWindow). По идее, следуя из того, что словари синхронизированы (хотя я до сих пор не понимаю, как это осуществляется, где и кем) в словаре _changedDepths появляется элемент В = (MarketDepth, QuotesWindow). Причем А и В имеют одно и тоже значение,а ключи разные, хотя и соответствуют одному и тому же инструменту.


Ну, дак в соответствии с Вами же процитированным кодом:

Код
private void TraderOnMarketDepthsChanged(IEnumerable<MarketDepth> depths)
{
    foreach (var depth in depths)
    {
         var wnd = _quotesWindows.TryGetValue(depth.Security);

         if (wnd != null)
         _changedDepths[depth] = wnd;
}
}


FlashPlayer Перейти
Далее в TraderOnMarketDepthsChanged по каждому обновленному MarketDepth делается что-то невероятное

Это не далее, уже проехали выше

FlashPlayer Перейти
Ведь можно, как вы сказали выше, вести только один словарь _changedDepths и в событии TraderOnMarketDepthsChanged делать то, что делается в _dispatcher.AddPeriodicalAction и результат по идее дб тот же

Делайте
Если бы мне нужно было бы, я бы сделал
Спасибо:

FlashPlayer

Фотография
Дата: 13.01.2013
Ответить


Понятно, то есть одним словом в примере немного мудрено реализован механизм?
Спасибо:


Добавить файлы через драг-н-дроп, , или вставить из буфера обмена.

loading
clippy