ObservableCollection和ProgressBar不能一起使用

时间:2018-07-16 04:42:35

标签: c# multithreading xaml

我想通过ObservableCollection方法将数据从文件导入Add。而且我还希望在加载文件期间,ProgressBar的属性IsIndeterminate应该为true

因此,我将有两个线程:

  1. 显示ProgressBar。
  2. 将数据从文件加载到ObservableCollection

但是我有两个问题。

  1. ObservableCollection在未创建线程中不起作用。

OR

  1. 如果我在第二个线程中使用Dispatcher,它将阻塞第一个线程,并且ProgressBar无法正常工作。

问题:

  1. 我能听到您的想法吗?您将如何解决这个问题?
  2. 如何结交朋友? :)

C#:

public ObservableCollection<MyClass> Collection { get; set; }
public bool ProgressIsIndeterminate { get; set; }
CommandImportBaseFile = new Command(async (o) => { await DoImportFileAsync(); });

private async Task DoImportBaseFileAsync()
{
    OpenFileDialog serviceDialogs = new OpenFileDialog();
    if (serviceDialogs.ShowDialog() == true)
    {
        string message = null;
        ProgressIsIndeterminate = true;
        try
        {
            var task = Task.Run(() =>
            {
                if (!System.IO.File.Exists(serviceDialogs.FileName)) throw new Exception("File is not found - " + serviceDialogs.FileName + ".");
                MyClass data = new MyClass();
                BinaryFormatter binFormat = new BinaryFormatter();
                using (Stream fStream = new FileStream(serviceDialogs.FileName, FileMode.Open, FileAccess.Read, FileShare.None))
                {
                    data = (MyClass)binFormat.Deserialize(fStream);
                }
                foreach (var d in data)
                {
                    Collection.Add(d);
                }
            });
            await task;
        }
        catch (Exception e)
        {
            message = e.Message;
        }

        if (String.IsNullOrEmpty(message))
        {
            message = "Completed";
        }
        MessageBox.Show(message);
        ProgressIsIndeterminate = false;
    }
}

或者我使用Dispatcher

foreach (var d in data)
{
    App.Current.Dispatcher.BeginInvoke((Action)delegate()
    {
        Collection.Add(d);
    });
}

XAML:

<ProgressBar
    Width="100"
    Height="12"
    IsIndeterminate="{Binding ProgressIsIndeterminate}"/>

更新

C#:

public class MyClass
{
    public string Name { get; set; }
    public int Age { get; set; }

    public MyClass()
    {
        Name = String.Empty;
        Age = 0;
    }
}

2 个答案:

答案 0 :(得分:4)

您要将长期运行的工作与更新UI分开。您的异步代码不应该负责执行更新UI。例如,您应该使用IProgress<T>来通知UI有关更改。

以下是示例:

public class MainWindowViewModel : INotifyPropertyChanged
    {
        private double _progressValue;
        public double ProgressValue {
            get => _progressValue;
            set
            {
                _progressValue = value;
                NotifyPropertyChanged();
            }
        }

        public ObservableCollection<string> Values { get; set; } = new ObservableCollection<string>();

        private ICommand _onLoadCommand;
        public ICommand OnLoadCommand => _onLoadCommand ?? 
                                         (_onLoadCommand = new RelayCommand(_ => true, p => OnLoad()));

        private async void OnLoad()
        {
            var progress = new Progress<Tuple<string, double>>();
            progress.ProgressChanged += OnWorkProgress;
            await DoWork(progress);
        }

        private void OnWorkProgress(object sender, Tuple<string, double> e)
        {
            Values.Add(e.Item1);
            NotifyPropertyChanged(nameof(Values));
            ProgressValue = e.Item2 * 100;
        }

        private async Task DoWork(IProgress<Tuple<string, double>> progress)
        {
            const int elementsCount = 500;
            for (int index = 0; index < elementsCount; index++)
            {
                var result = "Value_" + index;
                await Task.Delay(10);
                progress.Report(Tuple.Create(result, (double)index / elementsCount));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged([CallerMemberName]string propertyName = null) => 
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

并查看:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Loaded">
        <i:InvokeCommandAction Command="{Binding OnLoadCommand}" />
    </i:EventTrigger>
</i:Interaction.Triggers>
<StackPanel>
    <ProgressBar Minimum="0" Maximum="100" Value="{Binding ProgressValue}" Height="30"></ProgressBar>
    <Separator HorizontalAlignment="Center"></Separator>
    <ListView ItemsSource="{Binding Values}" Height="250"></ListView>
</StackPanel>

这是它的工作方式:

  1. OnLoad是Window的事件处理程序,在window时被调用 负载。这是开始使用异步代码的好地方。
  2. 我们创建Progress<T>实例,并将其用作 UI和ThreadPool线程之间进行通信。
  3. 我们在UI线程上订阅了ProgressChanged,这是一个 用于我们的交流
  4. 我们开始进行异步工作await DoWork(progress);并通过 引用progress
  5. 异步代码完成其工作,一旦完成大部分工作, 向UI线程(或任何侦听器)报告多少 使用progress.Report完成工作。
  6. 然后OnWorkProgress在UI线程上显示报告。

编辑: 如果要运行此程序,则需要System.Windows.Interactivity.WPF nuget程序包。

EDIT2:

这是为CPU绑定工作的方式:

private async void OnLoad()
{
    var progress = new Progress<Tuple<string, double>>();
    progress.ProgressChanged += OnWorkProgress;
    await Task.Run(() => DoWork(progress));
}

private void OnWorkProgress(object sender, Tuple<string, double> e)
{
    Values.Add(e.Item1);
    NotifyPropertyChanged(nameof(Values));
    ProgressValue = e.Item2 * 100;
}

private void DoWork(IProgress<Tuple<string, double>> progress)
{
    const int elementsCount = 500;
    for (int index = 0; index < elementsCount; index++)
    {
        var result = "Value_" + index;
        Thread.Sleep(10); // don't do this. Do CPU intensive work here...
        progress.Report(Tuple.Create(result, (double)index / elementsCount));
    }
}

答案 1 :(得分:-2)

使用后台线程,并使用runco​​mpleted和progress方法更新进度条。 ObservableCollection需要开始调用而不是调用来更新更改。希望这会阻止UI或您的线程