.net - MVVM 应用程序中的并发架构

标签 .net entity-framework architecture mvvm concurrency

我有一个客户端/服务器类型的应用程序设置,类似于 BitTorrent 下载程序。但是,种子会远程发送到客户端。

共享数据的主要部分是要下载的文件(种子)列表。

我必须同时处理这些情况:

  • 服务器(通过 WCF)发送要下载的更新文件列表,这意味着一些新文件将添加到列表中,一些从列表中删除(有些保持不变)
  • 同时,文件可能会完成下载/更改状态,因此列表中的项目需要在本地更新为新状态
  • 客户端上的本地事件可能会导致列表中的某些项目过期,因此应将其删除

我正在使用 MVVM 架构,但我相信 View 模型应该与 View 紧密映射,因此我添加了一个“服务”层,该层当前是一堆单例(我知道)。其中之一充当所述列表的共享资源,因此我有一个集合由多个线程更新。

我想放弃单例,转而使用依赖注入(inject)和不可变对象(immutable对象),以减少我所看到的死锁、“删除/分离对象”和数据完整性错误。

但是,我几乎不知道在哪里“保存”列表以及如何管理来自不同线程的传入事件,这些事件可能会取消/否定/覆盖列表的当前处理。

我正在寻找有关在高层处理此类场景的指导。

我正在对列表中的项目使用 Entity Framework ,因为数据也需要保留。

最佳答案

我最近为 Windows 服务检查器做了类似的事情。它最终也很容易实现。

就您的情况而言,我认为需要以下内容。

文件 - 其唯一目的是下载文件并通知更改。
文件管理器 - 维护文件列表并添加新文件、删除文件等。

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));
            }
        }
    }

这样做的好处是您永远不需要从后台线程更新 UI。您要更新的是只读属性,只有后台类也会写入这些属性。此类之外的任何内容都只能读取,因此您不必担心锁定。当 PropertyChanged 引发时,UI 绑定(bind)系统将收到属性已更改的通知,然后读取该值。

现在轮到经理了

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);
        }
    }

现在,您在 View 模型中需要做的就是从 FileManager 中公开 ListOfFiles,这是一个可观察的集合。来自它的通知将使绑定(bind)系统知道 UI 何时需要更新。

只需将 ListOfFiles 绑定(bind)到 ListView 或类似的文件,为 File 类添加一个数据模板,这将使 ListView 知道如何呈现每个文件。

您的 WCF 服务器和 View 模型应该引用相同的文件管理器,WCF 添加和删除文件, View 模型使 ListOfFiles 可用于 UI。
这只是为了让大家理解这个概念而进行的一个粗略的修改。您需要添加您认为合适的内容。

请告诉我这是否有帮助。

关于.net - MVVM 应用程序中的并发架构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6677538/

相关文章:

entity-framework - TFS VNext 构建和 Entity Framework 迁移 - Visual Studio Team Services(原为 Visual Studio Online)

macos - 32 位应用程序如何在 64 位 Mac 上运行?

.NET 4 加载不同于 .NET 3.5 的程序集

c# - 免费报表设计器(生成器),如 Crystal Report、Fast Report 等

c# - Dapper 可以从数据库表创建模型类吗?

c# - 使用多个分组依据字段时按日期分组的 Linq

oop - 如何设计索引表?

asp.net - MVC 3 应用程序中的模型、 View 模型、DTO

c# - 反射器和编译器生成的代码

c# - CommandBarButton 图标变得黑色背景