Silverlight и RX: почему мне нужно изменить размер браузера, чтобы обновить пользовательский интерфейс? - PullRequest
1 голос
/ 13 февраля 2012

Начнем с самого начала:

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

Во-первых, я написал свой собственный класс планировщика, ориентированный на события, с дескриптором ожидания и блокирующим объектом, чтобы ограничить количество параллельных потоков и дождаться всех потоков в конце, пока я не запустил конечное CalculationCompletedEvent (кстати: Я использовал Backgroundworkers для выполнения многопоточности). Но что-то там не было потокобезопасным, и количество возвращаемых элементов в списке результатов не было постоянным. Я решил не тратить больше времени на поиск утечки после того, как коллега указал мне на Reactive Extensions (rx).

Чтобы получить представление о том, как это использовать, я объединил пример потребителя-производителя с некоторыми советами о том, как использовать rx ( example1 и example2 ).

Это прекрасно работает, но я не понимаю: почему мне нужно изменить размер браузера, чтобы обновить список и показать элементы, содержащие «_receivedStrings»? Еще раз крошечное глупое пренебрежение?

Кстати: если вы не рекомендовали бы использовать rx, сделайте снимок и скажите мне, почему и что использовать еще.

XAML:

<UserControl x:Class="ReactiveTest.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <ListBox HorizontalAlignment="Stretch" Name="listBox1" 
                 VerticalAlignment="Stretch" ItemsSource="{Binding}"/>
        <Button Grid.Row="1" Content="Klick me!" Width="Auto" Height="Auto" 
                HorizontalAlignment="Center" Click="Button_Click"/>
    </Grid>
</UserControl>

Codebehind:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.IO;
using System.Reactive.Linq;

namespace ReactiveTest
{
    public partial class MainPage : UserControl
    {
        private int _parallelThreadsAmount;

        public IList<String> receivedStrings;

        public MainPage()
        {
            InitializeComponent();
            receivedStrings = 
                new List<String>();
            this._parallelThreadsAmount = 10;

            this.listBox1.DataContext = receivedStrings;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            IList<IObservable<String>> obsCollection = 
                new List<IObservable<String>>();

            foreach (var item in forums)
            {
                obsCollection.Add(Calculate(item));
            }
            DateTime start = DateTime.Now;

            obsCollection.Merge(this._parallelThreadsAmount)
                .Subscribe(
                    y =>
                    {
                        receivedStrings.Add(
                            String.Format("{0} - Received: {1}", receivedStrings.Count, y));
                    },
                    () =>
                    {
                        DateTime end = DateTime.Now;
                        TimeSpan elapsed = end - start;
                        this.receivedStrings.Add(
                            String.Format(
                                "{0}/{1} done in {2} ms.", 
                                receivedStrings.Count, 
                                forums.Count(), 
                                elapsed.TotalSeconds)
                            );
                    }
                );
        }

        IObservable<String> Calculate(String source)
        {
            Random rand = new Random();
            return Observable.Defer(() => Observable.Start(() =>
            {
                // simulate some work, taking different time, 
                // to get the threads end in an other order than they've been started                
                System.Threading.Thread.Sleep(rand.Next(500, 2000));
                return source;
            }));
        }


        static readonly String[] forums = new string[]
        {
            "announce",
            "whatforum",
            "reportabug",
            "suggest",
            "Offtopic",
            "msdnsandbox",
            "netfxsetup",
            "netfxbcl",
            "wpf",
            "regexp",
            "msbuild",
            "netfxjscript",
            "clr",
            "netfxtoolsdev",
            "asmxandxml",
            "netfx64bit",
            "netfxremoting",
            "netfxnetcom",
            "MEFramework",
            "ncl",
            "wcf",
            "Geneva",
            "MSWinWebChart",
            "dublin",
            "oslo",
            // … some more elements
        };
    }
}

Ответы [ 2 ]

1 голос
/ 13 февраля 2012

Почему-то мне хочется сказать, что вы немного запутались с Rx, что, честно говоря, вполне нормально:)

Как пишет Ли Кэмпбелл, есть несколько вещей для начала, проблема изменения размеравообще не имеет отношения к Rx, как я вижу, но это материал ObservableCollection.

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

private void Button_Click(object sender, RoutedEventArgs e)
{
    //extension method in Rx for wrapping
    var obsCollection = forums.ToObservable();

    //Observing on the dispatcher to prevent x-thread exceptions
    obsCollection.Select( Calculate ).ObserverOnDispatcher().Subscribe(
        receivedString => { 
                receivedStrings.Add( String.Format("{0} - Received: {1}", receivedStrings.Count, y) );
            },
            ()=>{
                DateTime end = DateTime.Now;
                TimeSpan elapsed = end - start;
                this.receivedStrings.Add(
                    String.Format( "{0}/{1} done in {2} ms.", receivedStrings.Count, forums.Count(), elapsed.TotalSeconds)
                );
            }
    );
}

Random rand = new Random();
IObservable<string> Calculate(string inputString)
{
    //launching the "calculation" on the taskpool...can be changed to other schedulers
    return Observable.Start(
        ()=>{
            Thread.Sleep(rand.Next(150,250));

            return inputString;
            }, Scheduler.ThreadPool
        );
}
1 голос
/ 13 февраля 2012

Игнорирование отсутствия MVVM, IoC, тестируемости и т. Д. ... Вы не реализуете INotifyPropertyChanged, вы не используете ObservableCollection (of T).1. Измените ваше общедоступное поле на общедоступное свойство, доступное только для чтения. 2. Используйте ObservableCollection вместо IList

//public IList<String> receivedStrings; 
private readonly ObservableCollection<string> _receivedStrings = new ObservableCollection<string>();
public ObservableCollection<string> ReceivedStrings
{
    get { return _receivedStrings;}
}

. Возможно, вам также придется использовать ObserveOnDispatcher (), чтобы гарантировать, что вы вызываетесь в Dispatcher, так как выне может обновить пользовательский интерфейс (даже через Binding) в потоке, который не является потоком диспетчера.

obsCollection.Merge(this._parallelThreadsAmount)                 
  .ObserveOn(Scheduler.Dispatcher)
  //-or-.ObserveOnDispatcher()
  //-or even better -.ObserveOn(_schedulerProvider.Dispatcher)
  .Subscribe(
...