C # — Хранение тиковых данных в свечах OHLC: значения не обновляются случайным образом, проблема с многопоточностью?

#c# #multithreading #xamarin.forms #entity-framework-core

#c# #многопоточность #xamarin.forms #сущность-фреймворк-ядро

Вопрос:

Я работал над хобби-проектом, разрабатываемым на C # Xamarin Forms Prism EF Core Sqlite, отладка в приложении UWP.

Я написал следующий код для хранения тиковых данных, полученных от брокера, в Sqlite.

Во-первых, обратный вызов OnTick, который получает тики (прибл. 1 тик в секунду на инструмент):

     private void OnTick(Tick tickData)
    {
        foreach (var instrument in IntradayInstruments.Where(i => i.InstrumentToken == tickData.InstrumentToken))
        {
            instrument.UpdateIntradayCandle(tickData);
        }
    }
 

И метод UpdateIntradayCandle:

     public void UpdateIntradayCandle(Tick tick)
    {
        if (LastIntradayCandle != null)
        {
            if (LastIntradayCandle.Open == 0m)
            {
                LastIntradayCandle.Open = tick.LastPrice;
            }
            if (LastIntradayCandle.High < tick.LastPrice)
            {
                LastIntradayCandle.High = tick.LastPrice;
            }
            if (LastIntradayCandle.Low == 0m)
            {
                LastIntradayCandle.Low = tick.LastPrice;
            }
            else if (LastIntradayCandle.Low > tick.LastPrice)
            {
                LastIntradayCandle.Low = tick.LastPrice;
            }
            LastIntradayCandle.Close = tick.LastPrice;
        }
    }
 

LastIntradayCandle является свойством:

     object _sync = new object();
    private volatile IntradayCandle _lastIntradayCandle;
    public IntradayCandle LastIntradayCandle
    {
        get
        {
            lock (_sync)
            {
                return _lastIntradayCandle;
            }
        }
        set
        {
            lock (_sync)
            {
                _lastIntradayCandle = value;
            }
        }
    }
 

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

     public void AddNewIntradayCandle()
    {
        if (LastIntradayCandle != null)
        {
            LastIntradayCandle.IsClosed = true;
        }
        var newIntradayCandle = new IntradayCandle { Open = 0m, High = 0m, Low = 0m, Close = 0m };
        LastIntradayCandle = newIntradayCandle;
        IntradayCandles.Add(newIntradayCandle);
    }
 

Теперь проблема в том, что я получаю 0 в тех открытых, высоких или низких, но не в закрытых, открытых с наибольшим количеством нулей. Это происходит очень случайно.

введите описание изображения здесь

Я думаю, что если какое-либо из значений Open, High, Low или Close обновляется, это означает, что галочка имеет значение для захвата, но каким-то образом одно или несколько назначений в методе UpdateIntradayCandle не выполняются. Наличие нулей — это строгое «НЕТ» для целей приложения.

Я не являюсь ни формально обученным программистом, ни экспертом, но являюсь любителем самообучения и определенно никогда раньше не пытался работать в многопоточности.

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

Комментарии:

1. Это выглядит как идеальный случай для реактивного программирования.

2. Привет @GertArnold, я не слишком знаком с этой концепцией. Не могли бы вы пояснить, что вы имеете в виду?

Ответ №1:

Многопоточность и ядро EF несовместимы. Контекст ядра EF не является потокобезопасным. Вы должны создать новый контекст для каждого потока. Кроме того, обеспечение потокобезопасности вашего объекта — пустая трата времени.

Итак, схематично вы должны сделать следующее, и вы можете удалить блокировки с вашего объекта.

 private void OnTick(Tick tickData)
{
    using var ctx = new MyDbContext(...);
    foreach (var instrument in ctx.IntradayInstruments.Where(i => i.InstrumentToken == tickData.InstrumentToken))
    {
        instrument.UpdateIntradayCandle(tickData);
    }
    ctx.SaveChanges();
}
 

Комментарии:

1. Привет @Svyatoslav Danyliv, как вы думаете, проблема в сохранении изменений? Если это так, то в базе данных должна отсутствовать целая свеча OHLC, а не только одно свойство без значения там, где оно должно его содержать. Я использовал блокировки объектов, думая, что свойство ‘IntradayCandle’ будет заменено новым экземпляром, пока в него записываются значения из другого потока. Кстати, основной контекст ef никогда не создавал никаких исключений при сохранении. Просто свойство ‘Open’ не получает своего значения.

2. @yoursvsr, объект не будет заменен. Контекст EF создает новую сущность, когда вы материализуете объекты, как вы делаете это int foreach . Он может кэшировать их в своем трекере изменений, но для каждого нового экземпляра контекста это будут новые объекты.

3. Пожалуйста, ознакомьтесь с методом AddNewIntradayCandle(), в котором я заменяю экземпляр новым каждые 5 м из другого потока, поступающего от объекта timer.

4. @yoursvsr, ничего не изменилось. Вы должны работать с одним контекстом на поток. Когда вы получаете объект из контекста — он принадлежит этому контексту. Похоже IntradayCandles , это свойство класса, но оно должно быть объектом, который вы запросили из контекста.

5. Да, IntradayCandles — это список<IntradayCandle> и является свойством класса. Новые объекты создаются в коде и добавляются в список для сохранения каждые 5 м, после создания и обновления из тиков. Итак, я прошу прощения за то, что повторяюсь, в случае, если проблема связана с контекстом EF, из БД должна пропасть целая свеча OHLC, а не только одно свойство без значения там, где оно должно его содержать. Не могли бы вы пролить немного света?