简体   繁体   中英

Wait for task to finish without blocking the UI, strange exception

I created a little app to rename some files using a basic mvvm pattern. The files are copied to a new folder with the new name and there is a progress bar showing the progress(successfully). Since there are a lot of files I am doing it via Task.Factory.StartNew(...) . The color of the progressbar is orange; when all files are renamed the color should turn to green.

this is part of the xaml:

<ProgressBar x:Name="pBar" HorizontalAlignment="Left" Height="20" Margin="10,5" Foreground="{Binding ProgBarBrush}" VerticalAlignment="Top" Width="100" Value="{Binding ProgBarValue}" Maximum="{Binding MaxIndex}"/>

the view model:

public class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };

    private int _maxIndex;
    public int MaxIndex
    {
        get { return _maxIndex; }
        set
        {
            if (value == _maxIndex)
                return;
            _maxIndex = value;
            PropertyChanged(this, new PropertyChangedEventArgs(nameof(MaxIndex)));
        }
    }

    private int _pBarValue;
    public int ProgBarValue
    {
        get { return _pBarValue; }
        set
        {
            if (value == _pBarValue)
                return;
            _pBarValue = value;
            PropertyChanged(this, new PropertyChangedEventArgs(nameof(ProgBarValue)));
        }
    }

    private SolidColorBrush _brush;
    public SolidColorBrush ProgBarBrush
    {
        get { return _brush; }
        set
        {
            if (value == _brush)
                return;
            _brush = value;
            PropertyChanged(this, new PropertyChangedEventArgs(nameof(ProgBarBrush)));
        }
    }
}

the task:

private void StartRename_Click(object sender, RoutedEventArgs e)
{
    var newDir = dir + @"\Renamed";
    Directory.CreateDirectory(newDir);

    vm.ProgBarValue = 0;
    vm.ProgBarBrush = new SolidColorBrush(Colors.Orange);

    Task t1 = Task.Factory.StartNew(() =>
    {
        foreach (var pic in pics)
        {
            if (File.Exists(newDir + @"\" + pic.newName))
            {
                MessageBox.Show("File " + newDir + @"\" + pic.newName + " already exists. Skipping remaining files...", "File already exists");
                return;
            }

            File.Copy(pic.fileInfo.FullName, newDir + @"\" + pic.newName);
            vm.ProgBarValue++;
        }
        vm.ProgBarBrush = new SolidColorBrush(Colors.Green);
    });

}

The binding works - the value of the progressbar changes as well as the first color change in the UI thread to orange. The files are also copied and renamed in the way I want. But when I'm doing the color change like this I get a strange "kind of exception" (Visual Studio 2017 simply stops):

Ihre App wurde angehalten, aber es gibt keinen anzuzeigenden Code, da alle Threads externen Code ausgeführt haben (normalerweise System- oder Frameworkcode).

Your app was halted, but there is no code to be shown because all threads ran external code (usually System or Framworkcode) => this is my own translation

When I'm doing it with ContinueWith I get the same "exception". Using Task.WaitAll() and then doing the color setting in the UI-thread freezes the UI. How can I change the color when all files are renamed without freezing the UI?

Thanks for your help.

You need to change the progress bar color and value from the UI thread. You can easily do that with async/await as shown in this sample. Also, I updated the progress using IProgress .

private async void StartRename_Click(object sender, RoutedEventArgs e)
{
    var newDir = dir + @"\Renamed";
    Directory.CreateDirectory(newDir);

    vm.ProgBarValue = 0;
    vm.ProgBarBrush = new SolidColorBrush(Colors.Orange);
    var progress = new Progress<int>(_ => vm.ProgBarValue++)
    await Task.Run(() =>
    {
        foreach (var pic in pics)
        {
            if (File.Exists(newDir + @"\" + pic.newName))
            {
                MessageBox.Show("File " + newDir + @"\" + pic.newName + " already exists. Skipping remaining files...", "File already exists");
                return;
            }

            File.Copy(pic.fileInfo.FullName, newDir + @"\" + pic.newName);
            progress.Report(1);
        }                
    });
    vm.ProgBarBrush = new SolidColorBrush(Colors.Green);
}

As @ckuri mentioned in the comments an IO bound operation is properly done with an async api. See this reference on how to further improve the code. The very first example given is that of copying a file asynchronously.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM