Архитектура параллелизма в приложении MVVM - PullRequest
2 голосов
/ 13 июля 2011

У меня есть приложение типа клиент / сервер, похожее на программу загрузки bittorrent. Однако торренты отправляются клиенту удаленно.

Основной частью общих данных является список файлов (торрентов), которые нужно загрузить.

Я должен обрабатывать эти случаи одновременно:

  • Сервер отправляет (через WCF) обновленный список файлов для загрузки, что означает, что некоторые новые файлы должны быть добавлены в список, а некоторые удалены из списка (а некоторые остаются без изменений)
  • В то же время файлы могут завершать загрузку / изменять состояние, поэтому элементы в списке должны обновляться локально с новыми состояниями
  • Локальные события на клиенте могут привести к истечению срока действия некоторых элементов в списке, поэтому их следует удалить

Я использую архитектуру MVVM, но я считаю, что модель представления должна отображаться близко к представлению, поэтому я добавила слой 'services', который в настоящее время представляет собой группу Singletons (я знаю). Один из которых выступает в качестве общего ресурса для указанного списка, поэтому у меня одна коллекция обновляется несколькими потоками.

Я хочу отойти от Singletons в пользу внедрения зависимостей и неизменяемых объектов, чтобы уменьшить взаимоблокировки, «удаленный / отсоединенный объект» и ошибки целостности данных, которые я видел.

Однако я мало представляю, где «хранить» список и как управлять входящими событиями из разных потоков, которые могут отменять / отменять / отменять текущую обработку списка.

Я ищу указатели на обработку такого сценария на высоком уровне.

Я использую Entity Framework для элементов списка, так как данные также должны быть сохранены.

Ответы [ 2 ]

2 голосов
/ 15 июля 2011

Я недавно сделал нечто подобное для проверки службы Windows.Это оказалось очень простым в реализации.

В вашем случае я вижу необходимость в следующем.

Файл - его единственная цель - загрузить файл и уведомить об изменениях.
FileManager - ведет список файлов и добавляет новые, удаляя ect.

public class File : INotifyPropertyChanged
    {
        private readonly string _fileName;
        private Thread _thread;
        private Task _task;
        private bool _cancelled;

        private TaskStatus _taskStatus;
        private int _taskProgress;
        private int _taskTotal;

        public event PropertyChangedEventHandler PropertyChanged;

        public File(string fileName)
        {
            _fileName = fileName;
            TaskStatus = TaskStatus.NotStarted;
        }

        public TaskStatus TaskStatus
        {
            get { return _taskStatus; }
            private set
            {
                _taskStatus = value;
                PropertyChanged.Raise(this, x => x.TaskStatus);
            }
        }

        public int TaskProgress
        {
            get { return _taskProgress; }
            private set
            {
                _taskProgress = value;
                PropertyChanged.Raise(this, x => x.TaskProgress);
            }
        }
        public int TaskTotal
        {
            get { return _taskTotal; }
            private set
            {
                _taskTotal = value;
                PropertyChanged.Raise(this, x => x.TaskTotal);
            }
        }

        public void StartTask()
        {
            _cancelled = false;

            //.Net 4 - task parallel library - nice
            _task = new Task(DownloadFile, TaskCreationOptions.LongRunning);
            _task.Start();

            //.Net Other
            _thread = new Thread(DownloadFile);
            _thread.Start();
        }

        public void CancelTask()
        {
            _cancelled = true;
        }

        private void DownloadFile()
        {
            try
            {
                TaskStatus = TaskStatus.Running;

                var fileLength = _fileName.Length;
                TaskTotal = fileLength;

                for (var i = 0; i < fileLength; i++)
                {
                    if (_cancelled)
                    {
                        TaskStatus = TaskStatus.Cancelled;
                        return;
                    }

                    //Some work to download the file
                    Thread.Sleep(1000); //sleep for the example instead 

                    TaskProgress = i;
                }

                TaskStatus = TaskStatus.Completed;

            }
            catch (Exception ex)
            {
                TaskStatus = TaskStatus.Error;
            }
        }
    }

    public enum TaskStatus
    {
        NotStarted,
        Running,
        Completed,
        Cancelled,
        Error
    }

    public static class NotifyPropertyChangedExtention
    {
        public static void Raise<T, TP>(this PropertyChangedEventHandler pc, T source, Expression<Func<T, TP>> pe)
        {
            if (pc != null)
            {
                pc.Invoke(source, new PropertyChangedEventArgs(((MemberExpression)pe.Body).Member.Name));
            }
        }
    }

Прелесть этого в том, что вам никогда не нужно обновлять пользовательский интерфейс из фонового потока.То, что вы обновляете, это свойства только для чтения, которые будет записывать только фоновый класс.Все, что находится вне этого класса, может только читать, так что вам не нужно беспокоиться о блокировке.Система привязки пользовательского интерфейса получит уведомление об изменении свойства при поднятии PropertyChanged, а затем прочитает значение.

Теперь для менеджера

public class FileManager
    {
        public ObservableCollection<File> ListOfFiles { get; set; }

        public void AddFile(string fileName)
        {
            var file = new File(fileName);
            file.PropertyChanged += FilePropertyChanged;
            file.StartTask();
            ListOfFiles.Add(file);
        }

        void FilePropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "TaskStatus")
            {
                var file = (File) sender;
                if (file.TaskStatus==TaskStatus.Completed)
                {
                    RemoveFile(file);// ??? automatically remove file from list on completion??
                }
            }
        }

        public void RemoveFile(File file)
        {
            if (file.TaskStatus == TaskStatus.Running)
            {
                file.CancelTask();
            }
            //unbind event
            file.PropertyChanged -= FilePropertyChanged;
            ListOfFiles.Remove(file);
        }
    }

Теперь все, что вам нужно сделать в вашей модели представления, это открыть ListOfFiles из FileManager, который является наблюдаемой коллекцией.Уведомления от него сообщат системе привязки, когда пользовательский интерфейс должен обновить.

Просто свяжите ListOfFiles с ListView или аналогичным, добавьте таблицу данных для класса File, которая позволит представлению списка знать, как отображать каждый из них.file.

Ваш сервер WCF и модель представления должны иметь ссылку на тот же File Manager, WCF добавляет и удаляет файлы, viewmodel делает ListOfFiles доступными для пользовательского интерфейса.
Это просто грубый взлом, чтобы донести концепцию.Вам нужно будет добавлять свои материалы по своему усмотрению.

Дайте мне знать, если это помогло.

1 голос
/ 13 июля 2011

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

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

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