使用 SSH.NET 和 WPF MVVM 并行文件上传的进度条

Progress bar for parallel file upload with SSH.NET and WPF MVVM

我正在尝试构建一个 WPF 应用程序,该应用程序使用 SSH.NET 在服务器上并行上传多个文件并为每个文件显示一个进度条。

在视图中我有以下数据网格:

                <DataGrid
                    AlternatingRowBackground="LightGray"
                    AutoGenerateColumns="False"
                    CanUserAddRows="False"
                    CanUserDeleteRows="False"
                    IsReadOnly="True"
                    ItemsSource="{Binding Files, Mode=TwoWay}"
                    SelectedItem="{Binding SelectedFile, Mode=OneWayToSource}">
                    <DataGrid.Columns>
                        <DataGridTextColumn Binding="{Binding Path=DisplayName}" Header="File" />
                        <DataGridTextColumn Binding="{Binding Path=FileSize}" Header="Size" />
                        <DataGridTextColumn Binding="{Binding Path=IsUploaded, Mode=OneWay}" Header="Uploaded" />
                        <DataGridTextColumn Binding="{Binding Path=IsUploading, Mode=OneWay}" Header="Uploading" />
                        <DataGridTemplateColumn Header="Progress">
                            <DataGridTemplateColumn.CellTemplate>
                                <DataTemplate>
                                    <Grid>
                                        <ProgressBar
                                            Width="300"
                                            Height="10"
                                            Maximum="{Binding FileSize}"
                                            Value="{Binding Path=SizeUploaded, Mode=OneWay}" />
                                        <TextBlock
                                            Margin="0,5,0,0"
                                            HorizontalAlignment="Center"
                                            Text="{Binding Path=PercentageCompleted, StringFormat={}{0}%}" />
                                    </Grid>
                                </DataTemplate>
                            </DataGridTemplateColumn.CellTemplate>
                        </DataGridTemplateColumn>
                        <DataGridTemplateColumn Header="Start">
                            <DataGridTemplateColumn.CellTemplate>
                                <DataTemplate>
                                    <Button cal:Message.Attach="UploadFile" Content="Start" />
                                </DataTemplate>
                            </DataGridTemplateColumn.CellTemplate>
                        </DataGridTemplateColumn>
                        <DataGridTemplateColumn Header="Stop">
                            <DataGridTemplateColumn.CellTemplate>
                                <DataTemplate>
                                    <Button cal:Message.Attach="StopTransfer" Content="Stop" />
                                </DataTemplate>
                            </DataGridTemplateColumn.CellTemplate>
                        </DataGridTemplateColumn>
                        <DataGridTemplateColumn Header="Remove">
                            <DataGridTemplateColumn.CellTemplate>
                                <DataTemplate>
                                    <Button cal:Message.Attach="RemoveFile" Content="Remove" />
                                </DataTemplate>
                            </DataGridTemplateColumn.CellTemplate>
                        </DataGridTemplateColumn>
                    </DataGrid.Columns>

                </DataGrid>

ViewModel 具有 public 属性来绑定 DataGrid 的项目和 SelectedItem:

    public BindingList<UploadedFile> Files { get; set; }

    public UploadedFile SelectedFile
    {
        get { return _selectedFile; }
        set
        {
            _selectedFile = value;
            NotifyOfPropertyChange(() => SelectedFile);
        }
    }

要从硬盘驱动器获取文件,我正在使用 MvvmDialgos 库并调用此方法创建一个新的 UploadedFile 并将其添加到 Files 列表:

        public void SelectFile()
    {
        var success = OpenFileDialog(this);

        if (success)
        {
            var file = new UploadedFile
            {
                FullName = OpenFileDialogSettings.FileName
            };

            file.DisplayName = Path.GetFileName(file.FullName);

            var fileInfo = new FileInfo(file.FullName);
            file.FileSize = (int)fileInfo.Length;

            if (Files == null)
                Files = new BindingList<UploadedFile>();

            Files.Add(file);

            NotifyOfPropertyChange(() => Files);
        }

    }

上传文件到服务器,SSH.NET我正在做以下事情:

    private async Task UploadFileToServer(ConnectionModel connection, string fileName, string destinationPath)
    {
        if (!string.IsNullOrWhiteSpace(destinationPath))
        {
            await Task.Run(() =>
            {
                var currentUploadedFile = Files.FirstOrDefault(x => x.FullName == fileName);

                currentUploadedFile.IsUploading = true;
                try
                {
                    _fileManager.UploadFile(connection, fileName, destinationPath);

                    currentUploadedFile.IsUploaded = true;
                }

                catch (Exception ex)
                {
                    BroadCastErrorMessage(ex.Message);
                    IsConnectionTabExpanded = true;
                }
                finally
                {
                    currentUploadedFile.IsUploading = false;
                }
            });
        }

        else
        {
            BroadCastErrorMessage("ERROR: You must enter a destination folder!");
            IsConnectionTabExpanded = true;
        }
    }

FileManager 调用的实际上是 ssh.net 库:

                        using (var fs = new FileStream(fileName, FileMode.Open))
                        {
                            fullFileName = fileName;
                            sftp.BufferSize = 4 * 1024;
                            sftp.UploadFile(fs, Path.GetFileName(fileName), true, uploadCallback: UpdateProgresBar);
                            OnFileUploadedSuccess($@"Successfully uploaded {fileName} in {path}.");
                            status = FileRenamedStatus.Uploaded;
                        }

并引发具有上传大小的事件:

    private void UpdateProgresBar(ulong uploaded)
    {
        OnFileUploading(uploaded);
    }

   protected void OnFileUploading(ulong uploaded)
    {
        FileUploading?.Invoke(this, new UploadingEventArgs(new UploadedFile
        {
            SizeUploaded = (int)uploaded,
            FullName = fullFileName
        }));
    }

在 ViewModel 中,我正在监听事件并更新进度条:

    private void OnUploading(object sender, UploadingEventArgs e)
    {
        foreach (var item in Files.ToList())
        {
            if (item.FullName == e.UploadedFile.FullName)
            {
                item.SizeUploaded = e.UploadedFile.SizeUploaded;

                var percentage = (double)e.UploadedFile.SizeUploaded / item.FileSize;

                item.PercentageCompleted = Math.Round(percentage * 100, 2);
            }
        }
    }

我的问题是,当我开始第二个文件传输时,第一个文件的进度条停止,第二个文件传输的进度条变得疯狂,显示随机值,增加和减少。我的猜测也是以某种方式添加了第一个文件的进度。

不知怎么的,我无法将每个进度条的进度分开。

我做错了什么?

我不确定这是否是正确的修复,但似乎每次我调用 UploadFileToServer() 时从 IoC(使用 Caliburn.Micro)获取 _fileManager 的新实例已解决进度条问题:

var manager = IoC.Get<IFileTransferManager>(); manager.OnUploading += OnUploading;