Dispatcher.BeginInvoke()未异步运行

时间:2016-02-27 18:14:17

标签: c# wpf asynchronous task dispatcher

这是我想要做的简化版本:

onClick buttonaNewMethod()将异步运行以保持UI响应。那就是它!

我已经阅读了一些答案,这就是我能想到的:

    private async void button_Click(object sender, RoutedEventArgs e)
    {
        Task task = Task.Run(() => aNewMethod());
        await task;
    }

    private void aNewMethod()
    {
        if (progress.Value == 0)
        {
            //Heavy work
            for (int i = 1; i < 1000000000; i++) { }
            progress.Value = 100;
        }
    }

正如您可能想到的那样,这会在System.InvalidOperationException if(progress.Value== 0)处抛出Dispatcher.BeginInvoke()

  

调用线程无法访问此对象,因为它不同   线程拥有它。

经过一些谷歌搜索,我已经读过我需要一个 private void aNewMethod() { Dispatcher.BeginInvoke((Action)delegate { if (progress.Value == 0) { //Heavy work for (int i = 1; i < 1000000000; i++) { } progress.Value = 100; } }); } 方法来更新/使用UI控件,所以我这样做了:

System.InvalidOperationException

这解决了for loop,但它没有异步运行,因为用户界面仍然冻结在aNewMethod();

所以问题是:如何异步运行import UIKit class ViewController: UIViewController { @IBOutlet weak var button: UIButton! @IBOutlet weak var textView: UITextView! override func viewDidLoad() { super.viewDidLoad() textView.delegate = self if (textView.text.isEmpty) { button.enabled = false } } } extension ViewController: UITextViewDelegate { func textView(textView: UITextView, range: NSRange, replacementText text: String) -> Bool { if (!textView.text.isEmpty) { button.enabled = true } else { button.enabled = false } return true } } 并仍然更新并与UI控件交互?

3 个答案:

答案 0 :(得分:2)

Dispatcher在UI线程中运行。它处理您的UI,并执行您通过BeginInvoke等传递给它的操作。 但它一次只能处理一件事;当它忙于处理您的操作时,它不会在此期间更新UI,因此UI会在此期间冻结。

如果要保持UI响应,则需要在单独的非UI线程中运行重载函数。在另一个线程上运行的那些函数中,您可以在需要访问UI时调用调度程序 - 理想情况下,只是为了更新UI元素而非常简短。

换句话说,你想要在一个单独的线程中运行你的sleep函数,然后在你需要设置进度值时从你自己的线程调用Dispatcher。像

这样的东西
private void button_Click(object sender, RoutedEventArgs e)
{
    Task task = Task.Run(() => aNewMethod());  // will call aNewMethod on the thread pool
}

private void aNewMethod()
{
    double progressValue = 0;
    Dispatcher.Invoke(() => progressValue = progress.Value);

    if (progressValue == 0)
    {
        Thread.Sleep(3000); // still executes on the threadpool (see above), so not blocking UI
        Dispatcher.Invoke(() => progress.Value = 100 );  // call the dispatcher to access UI
    }
}

答案 1 :(得分:2)

使用当前的实现,不需要使用Thread.Start,因为在那个方法中你只是睡了一段时间并访问那里不允许的UI线程对象 在你的场景中,一个更好的方法是你不应该使用Thread.Sleep而不是你应该使用Task.Delay这样做:

private async void button_Click(object sender, RoutedEventArgs e)
{
        if (progress.Value == 0)
        {
             await Task.Delay(3000); 
             progress.Value = 100;
        } 


}

现在你不需要Dispatcher.Invoke,并且在等待将在我们所做的同一个调用同步上下文中执行之后,信用转到asyncawait关键字作为语句异步调用,在这种情况下是UI线程。

答案 2 :(得分:2)

  

onClick一个按钮,aNewMethod()将异步运行以保持UI响应。

实际上,它在后台线程上同步运行 Task.Run非常适合此。

  经过一些谷歌搜索,我已经读过我需要一个Dispatcher.BeginInvoke()方法来更新/使用UI控件

不幸的是,这是谷歌肯定会误导你的一个方面。 Dispatcher 不是这里的最佳解决方案。

相反,您应该使用IProgress<T> / Progress<T>,因此:

private async void button_Click(object sender, RoutedEventArgs e)
{
  var progress = new Progress<int>(value => { this.progress.Value = value; });
  await Task.Run(() => aNewMethod(progress));
}

private void aNewMethod(IProgress<int> progress)
{
  //Heavy work
  for (int i = 1; i < 1000000000; i++) { }
  progress.Report(100);
}