简体   繁体   English

Dispatcher.BeginInvoke()不异步运行

[英]Dispatcher.BeginInvoke() not running asynchronously

Here's a simplified version of what I want to do: 这是我想要做的简化版本:

onClick a button , a aNewMethod() would run asynchronously to keep UI responsive. buttonaNewMethod()将异步运行以保持UI响应。 That's it! 而已!

I've read some answers and here's what i could come up with : 我已经阅读了一些答案,这是我能想到的:

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

As you may have thought, this throws a System.InvalidOperationException at if(progress.Value== 0) saying : 您可能已经想到,这会在if(progress.Value== 0)处抛出System.InvalidOperationException ,其内容if(progress.Value== 0)

The calling thread cannot access this object because a different thread owns it. 调用线程无法访问该对象,因为其他线程拥有它。

after some Googling, I've read that I need a Dispatcher.BeginInvoke() method to update/use UI controls, so I did this : 经过一番谷歌搜索后,我读到我需要一个Dispatcher.BeginInvoke()方法来更新/使用UI控件,所以我这样做了:

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

This solved the System.InvalidOperationException but it's not running asynchronously as the UI still freezes at for loop 这解决了System.InvalidOperationException但是由于UI仍然冻结在for loop因此它不是异步运行的

So the question is : How to run the aNewMethod(); 因此问题是:如何运行aNewMethod(); asynchronously and still update and interact with UI controls ? 异步并且仍在更新并与UI控件交互?

The Dispatcher runs in the UI thread. 分派器在UI线程中运行。 It handles your UI, and executes actions you pass to it via BeginInvoke etc. But it can only handle one thing at a time; 它处理您的UI,并执行通过BeginInvoke等传递给它的操作。但是它一次只能处理一件事情;它只能处理一件事情。 when it's busy handling your action, it won't update the UI in the meantime, so the UI freezes in the meantime. 在忙于处理您的操作时,它不会同时更新UI,因此UI会同时冻结。

If you want to keep your UI responsive, you'd need to run the heavy load functions in a seperate, non-UI thread. 如果要使UI保持响应状态,则需要在单独的非UI线程中运行重载函数。 Within those functions running on another thread, you can call the dispatcher whenever you need access to the UI - ideally, only very briefly for the purpose of updating UI elements. 在另一个线程上运行的那些函数中,您可以在需要访问UI时调用调度程序-理想情况下,只是为了更新UI元素而非常简短。

So in other words, you'd want to be running your sleep function in a seperate thread, and then just make a call to the Dispatcher from your own thread when you need to set the progress value. 因此,换句话说,您希望在单独的线程中运行sleep函数,然后在需要设置进度值时从您自己的线程中调用Dispatcher。 Something like 就像是

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

With you current implementation, there is no need to use Thread.Start , as in that method you are just sleeping it for some time and accessing UI thread objects there which is not allowed .In your scenario a better way of doing is that you should not use Thread.Sleep instead of that you should be doing it with Task.Delay like: 在当前的实现中,不需要使用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;
        } 


}

Now you don't need Dispatcher.Invoke , and credit goes to async and await keywords as statements after await will be executing in the same calling synchronization context from where we did async call which is UI thread in this case. 现在您不再需要Dispatcher.Invoke ,功劳asyncawait关键字,因为await之后的语句将在我们进行异步调用的位置(在本例中为UI线程)所在的同一调用同步上下文中执行。

onClick a button, a aNewMethod() would run asynchronously to keep UI responsive. 单击按钮后,aNewMethod()将异步运行以保持UI响应。

Actually, it's running synchronously on a background thread . 实际上,它在后台线程同步运行。 Task.Run is perfectly appropriate for this. Task.Run非常适合于此。

after some Googling, I've read that I need a Dispatcher.BeginInvoke() method to update/use UI controls 谷歌搜索后,我读到我需要一个Dispatcher.BeginInvoke()方法来更新/使用UI控件

Unfortunately, this is one area where Google will certainly mislead you. 不幸的是,这是Google肯定会误导您的领域。 Dispatcher is not the best solution here. Dispatcher不是这里的最佳解决方案。

Instead, you should use IProgress<T> / Progress<T> , as such: 而是应使用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);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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