none
Как поведут себя методы AsParallel<T>().ForAll<>(), если в исходной коллекции к которой они применяются вдруг окажется только один элемент? RRS feed

  • Вопрос

  • Здравствуйте.

    Скажите пожалуйста следующее. В программе на C# есть вот такая конструкция:

    Workspaces.AsParallel<WorkspaceViewModel>().ForAll<WorkspaceViewModel>(workspace =>
    {
           // Некоторая вычислительно-интенсивная операция "A";
           // Некоторая вычислительно-интенсивная операция "B";
    });

    Здесь Workspaces это ObservableCollection. К ней применён PLinq. В большинстве ситуаций в программе в Workspaces содержится много элементов WorkspaceViewModel, которые нужно обработать с помощью операций "А" и "В". Но в очень-очень редких случаях она может содержать только один элемпент. Вопрос следующий: В случае, если в коллекции находится только один элемент, будет ли конструкция "AsParallel().ForAll()" обрабатывать его правильно? То есть: будут ли операции "A" and "B" применяться к элементу коллекции в этом случае?

    18 июля 2013 г. 8:37

Ответы

  • Добрый день, коллега.

    Не очень понятен вопрос. Тот метод, который передается в качестве параметра в ForAll применяется ко всем элементам коллекции. Можно еще раз, в чем проблема? Или вы боитесь, что для обработки не будет создан отдельный поток и у вас произойдет "зависание" пользовательского интерфейса? Так вот, все нормально, для обработки даже одного элемента будет создан отдельный Therad.

    • Помечено в качестве ответа TownSparrow 19 июля 2013 г. 6:45
    18 июля 2013 г. 9:29
    Отвечающий

Все ответы

  • Добрый день, коллега.

    Не очень понятен вопрос. Тот метод, который передается в качестве параметра в ForAll применяется ко всем элементам коллекции. Можно еще раз, в чем проблема? Или вы боитесь, что для обработки не будет создан отдельный поток и у вас произойдет "зависание" пользовательского интерфейса? Так вот, все нормально, для обработки даже одного элемента будет создан отдельный Therad.

    • Помечено в качестве ответа TownSparrow 19 июля 2013 г. 6:45
    18 июля 2013 г. 9:29
    Отвечающий
  • У меня следующая ситуация:

    Пишу приложение по шаблону MVVM. Есть модель, есть представление и есть модель представления, выраженная двумя классами: FuturesTradeViewModel and MainWindowViewModel. Ниже я привожу их определения, но не полностью, т.к. они большие.

    Вот класс FuturesTradeViewModel:

    /// <summary>
    /// Класс FuturesTradeViewModel содержит свойства привязки
    /// и бизнес-логику торговли фьючерсным инструментом.
    /// </summary>
    class FuturesTradeViewModel
    {
       #region Properties
       /// <summary>
       /// Значение котировки инструмента.
       /// </summary>
       public Decimal Price { get; set; }
       /// <summary>
       /// Некоторая пороговая точка для значения котировки.
       /// </summary>
       public Decimal ThresholdPoint {get; set;}
       /// <summary>
       /// Флаг пересечения пороговой точки.
       /// </summary>
       public Boolean F_ThresholdExceeded { get; set; }
    
    }

    FuturesTradeViewModel представляет торговлю одним фьючерсом (торгуемым инструментом). В приложении необходимо осуществлять параллельную торговлю несколькими фьючерсами, соответственно для каждого из них создаётся свой экземпляр FuturesTradeViewModel. Все они содержатся в  ObservableCollection, которая находится в класса MainWindowViewModel. Ниже привожу его определение.

    Класс MainWindowViewModel:

    /// <summary>
    /// Класс MainWindowViewModel это data context
    /// для главного окна приложения.
    /// </summary>
    class MainWindowViewModel
    {
        #region Fields
        /// <summary>
        /// Коллекция рабочих пространств (торговлей фьючерсами), визуализируемая в главном окне приложения.
        /// </summary>
        private ObservableCollection<FuturesTradeViewModel> _workspaces;
        #endregion
    
        #region Properties
        /// <summary>
        /// Возвращает коллекцию рабочих пространств.
        /// </summary>
        public observableCollection Workspaces
        {
            get { rerturn _workspaces; }
        }
        #endregion
    
        #region Commands
        /// <summary>
        /// Возвращает команду, выполняющую подсоединение к бирже
        /// </summary>
        public ICommand ConnectToStockExchangeCommand
        {
            get
            {
              . . . . . . . . . . . . . . . . . .
                this.ConnectToStockExchange()
              . . . . . . . . . . . . . . . . . .
                return _connectToStockExchangeCommand;
            }
        }
        #endregion
    
        #region Methods
        /// <summary>
        /// Запускает TPL task для соединения с биржей
        /// и получения рыночной информации.
        /// </summary>
        private void ConnectToStockExchange()
        {
            Task tskConnectAndReceiveMarketData = Task.Factory.StartNew(ConnectAndReceiveMarketData,
                    TaskCreationOptions.LongRunning);
        }
    
        /// <summary>
        /// Метод, выполняемый в tskConnectAndReceiveMarketData.
        /// </summary>
        private void ConnectAndReceiveMarketData()
        {
            // Коннектор к бирже.
            Connection conn = new Connection();
            // Цикл проверки состояния коннектора (и получения рыночной инфы).
            while(true)
            {
                // Получить состояние коннектора:
                State state = conn.State;
                if (state == State.Active)
                {
                    if (Workspaces.Count > 0)
                    {
                        Task tskThresholdExceeding = Task.Factory.StartNew(CheckThresholdExceeding);
                    }
                }
            }
        }
    
        /// <summary>
        /// Проверка пересечения пороговой точки.
        /// </summary>
        private void CheckThresholdExceeding()
        {
            Workspaces.AsParallel().ForAll(futuresTrade => 
            {
                // Сравнить новую котировку с полученной ранее.
                if(newPrice - futuresTrade.Price) > futuresTrade.ThresholdPoint)
                {
                    futuresTrade.F_ThresholdExceeded = true;
                    futuresTrade.Price = newPrice;
                    SendOrderToStockExchange();
                }
            });
        }
    
        /// <summary>
        /// ВЫставляет заявку на биржу.
        /// </summary>
        void SendOrderToStockExchange()
        {
            // Некоторые действия . . . .
        }
        #endregion
    } 
    

    Эти два класса модели представления я показал правда очень схематично.

    (Команда ConnectToStockExchangeCommand выполняется по щелчку кнопки в представлении - в UI.) Теперь. Я запускаю таск tskThresholdExceeding TPL из метода ConnectAndReceiveMarketData потому что цикл проверки состояния коннектора должен крутиться постоянно не отвлекаясь ни на что, в том  числе и на проверку пересечения пороговой точки. Поэтому, метод CheckThresholdExceeding() должен выполняться параллельно с методом ConnectAndReceiveMarketData().

    Теперь о проблемах. У меня такое впечатление, что как будто не работает конструкция AsParallel<T>().ForAll<T>(). Не вызывается SendOrderToStockExchange(). Я пробовал делать её вызов вне условия if(newPrice - futuresTrade.Price) > futuresTrade.ThresholdPoint), но она не вызывается. Сейчас в целях тестирования пробую торговать только один инструмент (да и программа, чкстно сказать, ещё не дописана под несколько). Может ли тут быть виноватым AsParallel<T>().ForAll<T>()?

    18 июля 2013 г. 10:53
  • Т.е. фактически получается, что я из таска №1, запущенного с опцией LongRunning создаю и запускаю еще один таск№2, а уж из этого таска №2 вызываю AsParallel<T>().ForAll<T>() для коллекции. Это допустимо? А таск №2 создаю для того, что бы не отвлекать таск №1 от его работы, т.е. чтобы не заставлять таск №1 обрабатывать PLinq, т.к. таск №1 должен контролировать состояние коннектора и получать маркет-данные. И в результате - ничего не работает.
    18 июля 2013 г. 11:48
  • А метод CheckThresholdExceeding вообще выполняется? Может не выполняются более ранние условия?

    if (state == State.Active)
                {
                    if (Workspaces.Count > 0)
                    {


    • Изменено Kirill Bessonov 18 июля 2013 г. 11:55 опечатка
    18 июля 2013 г. 11:54
  • Нет, условие

    if (state == State.Active)
    {
        if (Workspaces.Count > 0)
        {

    выполняется. Заодно хотел бы узнать - можно ли изнутри тела PLinq выполнять вызов метода, как это сделано у меня:

    Workspaces.AsParallel().ForAll(futuresTrade => 
    {
        // Сравнить новую котировку с полученной ранее.
        if(newPrice - futuresTrade.Price) > futuresTrade.ThresholdPoint)
        {
             futuresTrade.F_ThresholdExceeded = true;
             futuresTrade.Price = newPrice;
             // !!! ВЫЗОВ МЕТОДА ВНУТРИ PLinq !!!
             futuresTrade.SendOrderToStockExchange();
        }
    });

    Т.е. вызывается метод, определённый в классе элемента коллекции?



    • Изменено TownSparrow 18 июля 2013 г. 12:18 исправлено
    18 июля 2013 г. 12:16
  • Ничего не мешает...

    Но мне кажется вы слишком усложнили. Ваш метод ConnectAndReceiveMarketData уже работает в отдельном потоке, затем (т.е. на UI он уже не влияет), затем вы создаете еще поток и в этом потоке параллельно обрабатываете коллекцию. Вам не кажется, что есть лишние таски?

    18 июля 2013 г. 12:28
  • Всё правильно - на UI не влияет. Но метод ConnectAndReceiveMarketData должен проверять состояние коннектора и поступление рыночной информации и не должен отвлекаться на другие операции. Если я в нём буду обрабатывать торговый алгоритм робота, то это вызовет задержку в получении рыночной информации, какие-то её потери и создаст очередь сообщений, превышающую допустимый размер. Поэтому  я и запускаю из него ещё один таск, который и занимается проведением торгов. А поскольку торговля ведётся по нескольким инструментам и по каждому из них представлена своим рабочм пространством и эти рабочие пространства хранятся в коллекции, то я и делаю PLinq по этой коллекции. Только вот что-то не совсем хорошо получается.

    А внутри PLinq значит можно вызывать методы? А если этим методам передавать параметры, которые были ранее сформированы вне тела PLinq, в котором вызывается метод?


    • Изменено TownSparrow 18 июля 2013 г. 13:38 поправлено
    18 июля 2013 г. 13:08
  • Попробуйте такой вариант:

    /// <summary> /// Класс MainWindowViewModel это data context /// для главного окна приложения. /// </summary> class MainWindowViewModel { private Decimal newPrice; public MainWindowViewModel() {

    Workspaces = new ObservableCollection<FuturesTradeViewModel>(); Workspaces.CollectionChanged += WorkspacesOnCollectionChanged; } #region Properties /// <summary> /// Возвращает коллекцию рабочих пространств. /// </summary> public ObservableCollection<FuturesTradeViewModel> Workspaces { get; private set; } #endregion #region Methods private void WorkspacesOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) { switch (args.Action) { case NotifyCollectionChangedAction.Add: foreach (FuturesTradeViewModel futuresTrade in args.NewItems) { if ((newPrice - futuresTrade.Price) > futuresTrade.ThresholdPoint) { futuresTrade.F_ThresholdExceeded = true; futuresTrade.Price = newPrice; SendOrderToStockExchange(futuresTrade); } } break; } } /// <summary> /// ВЫставляет заявку на биржу. /// </summary> private static Task SendOrderToStockExchange(FuturesTradeViewModel futuresTradeViewModel) { return Task.Factory.StartNew(delegate(object o) { // Обработка }, futuresTradeViewModel); } #endregion }

    При добавлении нового Workspace в список, вы проверяете условия и, если требуется, запускаете обработку. Нет лишних потоков и все ясно.

    18 июля 2013 г. 13:56
  • Тут при добавлении нового Workspace в список не получится. Каждый Workspace, после его добавления, отображается в TabControl на своей вкладке в окне приложения. И перед тем как запустить торги по инструменту, я, на вкладке нового (добавленного) Workspace, выбираю из комбобокса торгуемый инструмент, а затем там же щёлкаю кнопку "Начать торги" и только тогда торговля по данному инструменту начинается. НО идею вы дали неплохую.
    • Изменено TownSparrow 19 июля 2013 г. 6:44 исправлено
    18 июля 2013 г. 14:08