Как заставить реагировать все приложение, а не отдельные элементы управления пользовательского интерфейса, используя async и await?

Я использую ключевые слова async и await, чтобы приложение было отзывчивым

У меня есть следующий код в View, когда свойство повышается в ViewModel.

ПРОСМОТР: событие нажатия кнопки

    private async void button1_Click(object sender, RoutedEventArgs e)
    {
        button1.IsEnabled = false;

        // Property Change
        _viewModel.Words = await Task.Factory.StartNew(() => File.ReadAllLines("Words.txt").ToList()); // takes time to read about 3 - 4 seconds

        switch (_viewModel.RadioButtonWordOrderSelection)
        {
            case MainWindowViewModel.RadioButtonWordOrderSelections.NormalOrder:
                break;

            case MainWindowViewModel.RadioButtonWordOrderSelections.ReverseOrder:
                await Task.Factory.StartNew(() =>
                                                {
                                                    var words = _viewModel.Words.ToList();
                                                    words.Reverse();
                                                    // Property Change
                                                    _viewModel.Words = words;
                                                });
                break;

            case MainWindowViewModel.RadioButtonWordOrderSelections.Shuffle:
                await Task.Factory.StartNew(() =>
                                                {
                                                    // Property Change
                                                    _viewModel.Words = _viewModel.Words.Shuffle().ToList();
                                                });
                break;
        }

        await Task.Factory.StartNew(() => DownloadSomething(_viewModel.Words)); // takes time to read about 30 - 40 seconds

        button1.IsEnabled = true;
    }

_viewModel.Progress обновляется в View

ПРОСМОТР: закрытый метод занимает 30-40 секунд

    private void DownloadSomething(IEnumerable<string> words)
    {
        // Property Change
        _viewModel.Progress = 0;

        foreach (var word in words)
        {
            // Property Change
            _viewModel.Word = word;
            try
            {
                // Some code WebClient download code here
            }
            catch (Exception e)
            {
                //Trace.WriteLine(e.Message);
            }

            // Property Change
            _viewModel.Progress++;
        }
    }

ПРОСМОТР: обработано событие изменения свойства

    void _viewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        try
        {
            switch(e.PropertyName)
            {
                case "Progress":
                    // Since its a different Thread
                    // http://msdn.microsoft.com/en-us/magazine/cc163328.aspx
                    // Sets the Value on a ProgressBar Control.
                    // This will work as its using the dispatcher

                    // The following works
                    //Dispatcher.Invoke(
                    //    DispatcherPriority.Normal, 
                    //    new Action<double>(SetProgressValue), 
                    //    _viewModel.Progress);

                    // from http://stackoverflow.com/questions/2982498/wpf-dispatcher-the-calling-thread-cannot-access-this-object-because-a-differen
                    progress1.Dispatcher.Invoke(
                        DispatcherPriority.Normal, 
                        new Action(() =>
                        {
                            progress1.Value = _viewModel.Progress;
                        })
                    );

                    // This will throw an exception 
                    // (it's on the wrong thread)
                    //progress1.Value = _viewModel.Progress;
                    break;

                case "Words":
                    // Since its a different Thread
                    // http://msdn.microsoft.com/en-us/magazine/cc163328.aspx
                    // Sets the Max Value on a ProgressBar Control.
                    // This will work as its using the dispatcher

                    // The following Works
                    //Dispatcher.Invoke(
                    //    DispatcherPriority.Normal,
                    //    new Action<double>(SetProgressMaxValue),
                    //    _viewModel.Words.Count);

                    // from http://stackoverflow.com/questions/2982498/wpf-dispatcher-the-calling-thread-cannot-access-this-object-because-a-differen
                    progress1.Dispatcher.Invoke(
                        DispatcherPriority.Normal, 
                        new Action(() =>
                        {
                            progress1.Maximum = _viewModel.Words.Count;
                        })
                    );

                    // This will throw an exception 
                    // (it's on the wrong thread)
                    //progress1.Maximum = _viewModel.Words.Count;
                    break;

                case "Word":
                    // Since its a different Thread
                    // http://msdn.microsoft.com/en-us/magazine/cc163328.aspx
                    // Sets the Contant on a Label Control.
                    // This will work as its using the dispatcher

                    // The following Works
                    //Dispatcher.Invoke(
                    //    DispatcherPriority.Normal,
                    //    new Action<string>(SetLastWordValue),
                    //    _viewModel.Word);

                    // from http://stackoverflow.com/questions/2982498/wpf-dispatcher-the-calling-thread-cannot-access-this-object-because-a-differen
                    labelLastWord.Dispatcher.Invoke(
                        DispatcherPriority.Normal, 
                        new Action(() =>
                        {
                            labelLastWord.Content = _viewModel.Word;
                        })
                    );

                    // This will throw an exception 
                    // (it's on the wrong thread)
                    //labelLastWord.Content = _viewModel.Word;
                    break;

                case "RadioButtonWordOrderSelection":
                    break;

                default:
                    throw new NotImplementedException("[Not implemented for 'Property Name': " + e.PropertyName + "]");
            }
        }
        catch(Exception ex)
        {
            MessageBox.Show(ex.Message + "\n" + ex.StackTrace);
        }
    }

Пользовательский интерфейс отлично обновляется для progressBar1 и labelLastWord! Однако я столкнулся с проблемой, когда progressBar1 и labelLastWord обновляют, остальная часть пользовательского интерфейса заморожена.

Есть ли способ исправить это?

Я очень ценю любую помощь!


person Community    schedule 01.11.2012    source источник
comment
В Wpf вы должны использовать команды, а не код. Затем вы можете легко выполнить async / await в Command.   -  person Jordan Kaye    schedule 02.11.2012
comment
Об отредактированном заголовке см. Если вопросы включают теги в свои заголовки.   -  person Matteus Hemström    schedule 02.11.2012
comment
@JordanKaye, можешь посоветовать мне хорошее место для начала работы с командами? Спасибо за ваш ответ!!!   -  person    schedule 02.11.2012


Ответы (1)


Я настоятельно рекомендую вам следовать рекомендациям, изложенным в документе по асинхронному программированию на основе задач. Это намного лучше, чем просто перенаправить работу в пул потоков через StartNew. Если у вас есть операции, связанные с ЦП, вы можете использовать Task.Run, но для всего остального используйте существующие async-готовые конечные точки.

Кроме того, я считаю полезным рассматривать всю виртуальную машину как находящуюся в контексте пользовательского интерфейса. Итак, PropertyChanged всегда вызывается в контексте пользовательского интерфейса. От этого, в частности, зависит привязка данных.

private async Task<List<string>> ReadAllLinesAsync(string file)
{
  var ret = new List<string>();
  using (var reader = new StreamReader(file))
  {
    string str = await reader.ReadLineAsync();
    while (str != null)
    {
      ret.Add(str);
      str = await reader.ReadLineAsync();
    }
  }
  return ret;
}

private async void button1_Click(object sender, RoutedEventArgs e)
{
  button1.IsEnabled = false;

  _viewModel.Words = await ReadAllLinesAsync("Words.txt");

  List<string> words;
  switch (_viewModel.RadioButtonWordOrderSelection)
  {
    case MainWindowViewModel.RadioButtonWordOrderSelections.NormalOrder:
      break;

    case MainWindowViewModel.RadioButtonWordOrderSelections.ReverseOrder:
      await Task.Run(() =>
      {
        words = _viewModel.Words.ToList();
        words.Reverse();
      });
      _viewModel.Words = words;
      break;

    case MainWindowViewModel.RadioButtonWordOrderSelections.Shuffle:
      await Task.Run(() =>
      {
        words = _viewModel.Words.Shuffle().ToList();
      });
      _viewModel.Words = words;
      break;
  }

  await DownloadSomething(_viewModel.Words);

  button1.IsEnabled = true;
}

private async Task DownloadSomething(IEnumerable<string> words)
{
  _viewModel.Progress = 0;

  foreach (var word in words)
  {
    _viewModel.Word = word;
    try
    {
      await ...; // async WebClient/HttpClient code here
    }
    catch (Exception e)
    {
      //Trace.WriteLine(e.Message);
    }

    _viewModel.Progress++;
  }
}

void _viewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
  try
  {
    switch(e.PropertyName)
    {
      case "Progress":
        progress1.Value = _viewModel.Progress;
        break;

      case "Words":
        progress1.Maximum = _viewModel.Words.Count;
        break;

      case "Word":
        labelLastWord.Content = _viewModel.Word;
        break;

      case "RadioButtonWordOrderSelection":
        break;

      default:
        throw new NotImplementedException("[Not implemented for 'Property Name': " + e.PropertyName + "]");
    }
  }
  catch(Exception ex)
  {
    MessageBox.Show(ex.Message + "\n" + ex.StackTrace);
  }
}

В заключение я рекомендую вам приобрести и внимательно прочитать книгу Джоша Смита о MVVM. Вы используете такие термины, как «View» и «ViewModel», но при использовании этих компонентов вы полностью избегаете всех преимуществ шаблона MVVM.

person Stephen Cleary    schedule 01.11.2012