如何使用async和等待使整个应用程序响应而不是单独的UI控件?

时间:2012-11-01 21:31:42

标签: c# wpf asynchronous async-await async-ctp

我正在使用asyncawait个关键字来启用应用响应

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.ProgressView

中得到更新

查看:私有方法需要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);
        }
    }

UI为progressBar1和labelLastWord完美更新!我遇到了一个问题,当progressBar1和labelLastWord正在更新时,其余的UI被冻结。

有解决方法吗?

我非常感谢任何帮助!

1 个答案:

答案 0 :(得分:1)

我强烈建议您遵循Task-based Async Programming document中的指南。这比通过StartNew将工作分流到线程池要好得多。如果具有CPU绑定操作,则可以使用Task.Run,但对于其他所有操作,请使用现有的async - 就绪端点。

此外,我发现将整个VM视为UI上下文非常有用。所以PropertyChanged总是在UI上下文中引发。数据绑定尤其取决于此。

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

作为结束语,我建议您购买并仔细阅读Josh Smith's MVVM book。您正在使用“View”和“ViewModel”之类的术语,但是您使用这些组件的方式是完全,从而避免了MVVM模式的所有优势。